Skip to content

ConnectSoft SaaS Platform — DDD Design Blueprint

Introduction & Principles

Why DDD for the ConnectSoft SaaS Platform

Domain-Driven Design (DDD) lets us scale a multi-product, multi-tenant platform without turning into a “big ball of services.” We model the business—tenants, products, editions, entitlements, billing, compliance—as explicit domains with clear boundaries, contracts, and ownership. That makes the platform evolvable (new products/editions), compliant (auditability, data residency), and safe for automation/AI (guardrails and event contracts).

Strategic DDD (the “where” and “why”)

  • Bounded Contexts (BCs): Each major capability (Tenant Management, Identity, SaaS Core Metadata, Billing, Config/Flags, Usage/Metering, Notifications, Audit/Compliance, AI Orchestration) is its own BC with its own model and language.
  • Context Map: We define the relationship between BCs (upstream/downstream, conformist, anti-corruption layer, published language, customer/supplier) and prefer event-first integration.
  • Ubiquitous Language (UL): Shared, versioned vocabulary per BC (e.g., Subscription, Edition, Entitlement, Quota). Terms never cross BCs implicitly; if they do, they cross through contracts.
  • Team Topologies alignment: Each BC is owned by a long-lived team (clear “code + contracts + SLOs” ownership).

Tactical DDD (the “how”)

  • Aggregates & Roots: The smallest consistency boundary protecting invariants. Examples: Tenant, Edition, Subscription, FlagSet, UsageMeter, AuditStream.
  • Entities & Value Objects: Entities have identity; VOs are immutable and equality-by-value (e.g., Money, Quota, Residency, EvaluationContext).
  • Domain Events: Each aggregate emits domain events (e.g., tenant.created, subscription.canceled) captured in an Outbox for reliable delivery.
  • Repositories & Specifications: Repositories load/save aggregate roots only; query use-cases rely on read models or whitelisted Specification objects (sorting/filtering/paging).
  • Domain Services: Encapsulate domain logic that doesn’t fit a single aggregate (e.g., proration rules). They are pure and state-free.
  • Application Services: Orchestrate commands/transactions across aggregates, call domain services, emit integration events, enforce authorization and idempotency.

Event-First Integrations & Anti-Corruption Layers

  • Event-First: State changes publish domain events to a durable bus. Downstream BCs build projections and trigger policies.
  • Commands/Queries: Cross-context commands are rare and fronted by ACLs; cross-context queries read published read models or documented APIs.
  • Anti-Corruption Layer (ACL): When integrating with external systems (payments, email/SMS, analytics) or another BC’s public model, the ACL translates foreign concepts to our UL and shields the domain from leakage.

Isolation, Consistency & Transactions

  • Strong inside, eventual outside: Aggregates enforce strong invariants inside a BC; cross-BC effects achieve eventual consistency via outbox/inbox, retry, and idempotency keys.
  • One command → one aggregate: Prefer commands that mutate exactly one aggregate root; multi-aggregate workflows use sagas/process managers at the application layer.
  • Versioning: Events and contracts are up-versioned (additive first). Breaking changes require new topics/paths.

Modeling Conventions (cross-cutting)

Identifiers

  • TenantId, UserId, SubscriptionId, etc. use ULID/GUID (string) and are opaque to clients.
  • Compound keys are modeled as VOs (TenantScopedId { tenantId, localId }) when needed.
  • Public IDs vs internal IDs: public are stable and expose no internals; internal may change.

Multi-Tenancy

  • Every storage and message path considers tenantId and (when applicable) regionResidency.
  • Reads/writes are tenant-scoped at the repository boundary; cross-tenant operations are explicit admin flows.

Invariants

  • Declared in aggregate constructors/mutators; validated before state mutation.
  • Examples: Subscription cannot activate without a valid Edition; TenantResidency cannot change after lock; FlagKey uniqueness per scope.

Enumerations

  • Use smart-enum pattern for business concepts: BillingCycle, PricingType, LimitResetPeriod, DeliveryStatus, LifecycleStatus, Scope.
  • Enumerations are part of the UL and versioned like contracts.

Value Objects

  • Immutable; factory methods validate constraints (e.g., Money.Create(currency, amount), Quota.Create(dim, limit)).
  • Provide behavior, not just data (e.g., Money.Add, Window.Contains).

Domain vs Application Services

  • Domain Service: implements business policy (pure functions, deterministic).
  • Application Service: orchestrates use-cases (authorization, transactions, outbox, retries).

Repositories

  • One repository per aggregate root type.
  • No ad-hoc dynamic queries. For read scenarios, expose read models or specification-based queries with whitelisting.

Events

  • Name: <noun>.<pastParticiple> (e.g., subscription.canceled).
  • Contract includes: eventId, occurredAt (UTC), aggregateId, tenantId, version, payload.
  • Persist via Outbox; delivered with at-least-once semantics; consumers are idempotent.

Policies & Sagas

  • Policies subscribe to events and issue commands (e.g., subscription.activated → “grant entitlements”).
  • Sagas handle long-running, multi-step workflows with compensations (e.g., “provision tenant” → rollback on failure).

Security & Compliance

  • Authorization at application service boundary (RBAC/ABAC), enriched with tenant, edition, entitlements.
  • Data classification + redaction applied in logging/events/audit.
  • Audit events correlate with request/trace IDs.

“How to read this document”

  • Cycle-by-cycle: Each subsequent section focuses on one bounded context, starting with a Context Summary, then:
    1. Ubiquitous Language (terms),
    2. Aggregates / Entities / VOs / Enums,
    3. Commands & Events,
    4. Policies/Sagas,
    5. Repository & Read Model,
    6. Contracts (API/events),
    7. Invariants & State Machines,
    8. Security & Compliance notes,
    9. Evolution & Versioning.
  • Traceability: Every contract and event references the owning BC and version. Cross-links to upstream/downstream BCs are called out explicitly.
  • Copy-paste ready: Code skeletons and diagrams are provided where relevant so teams can implement directly.

Glossary (seed)

  • Aggregate (Root): Cluster of domain objects with a single entry point that guards invariants.
  • Anti-Corruption Layer (ACL): Translation layer that protects a domain model from external/foreign models.
  • Bounded Context (BC): Explicit boundary where a particular domain model applies.
  • Conformist: A downstream BC that adopts the upstream’s published language to reduce translation cost.
  • Domain Event: A fact that something significant happened in the domain, emitted by aggregates.
  • Domain Service: Stateless business logic that doesn’t belong to a single aggregate.
  • Outbox/Inbox: Reliable messaging patterns for event publishing/consumption with idempotency.
  • Policy: An event handler that reacts to domain events and issues commands.
  • Saga/Process Manager: Orchestrates multi-step, long-running workflows across aggregates/BCs.
  • Ubiquitous Language (UL): Shared vocabulary used by domain experts and developers within a BC.

Strategic Capabilities, Bounded Contexts & Context Map

Platform Capabilities and Bounded Contexts

The SaaS platform is structured as a set of well-defined bounded contexts, each owning its own ubiquitous language, models, and contracts. Together they form the backbone of the ConnectSoft factory approach, ensuring isolation, scalability, and safe evolution.

  • Tenant Management — lifecycle of organizations using the platform. Responsible for tenant creation, region residency, lifecycle state, and contact data.
  • Identity — authentication, authorization, roles, scopes, service principals, and federation. Issues tokens enriched with tenant, edition, and entitlements.
  • SaaS Core Metadata — product catalog, editions, features, and SLAs. Defines the authoritative model for entitlements and what downstream systems may provision.
  • Billing — subscriptions, pricing models, invoices, usage-based rating, proration, and payments. Publishes entitlement updates and billing events.
  • Configuration & Feature Flags — runtime settings and flag evaluations across tenant and edition scopes. Provides evaluation APIs with low-latency cache.
  • Usage & Metering — intake and aggregation of usage events. Feeds billing and quota enforcement, ensures fairness, and publishes exceeded quota events.
  • Notifications & Extensibility — communication via email/SMS/push and outbound webhooks. Templating, tenant-scoped branding, and signed delivery.
  • Audit & Compliance — immutable audit streams, retention policies, data classification, and eDiscovery exports. Cross-cutting enforcement of compliance.
  • AI Orchestration — agentic automation flows and AI-assisted tasks. Guardrails, least-privilege tool grants, and PR-based mutation flows.

Collaboration Styles and Relationships

The contexts interact using patterns that preserve autonomy:

  • Event-first integration — state changes in one context publish events (via outbox) consumed by downstream contexts.
  • Conformist downstreams — when latency is critical, downstream contexts adopt the upstream’s published event schema.
  • Anti-corruption layers — when interfacing with external services (e.g., payment gateways, third-party identity providers), ACLs translate to/from the platform’s ubiquitous language.
  • Published language via APIs — some contexts expose documented APIs for queries or administrative actions, but the default is to consume events.
  • Webhooks — for tenant extensibility, the Notifications context delivers signed webhooks that external systems consume.

Upstream / Downstream Dynamics

  • Tenant Management is upstream to Identity, Core Metadata, and Billing — it establishes the tenant anchor.
  • SaaS Core Metadata is upstream to Billing, Config/Flags, and Identity — it defines entitlements and valid editions.
  • Billing consumes entitlements from Metadata and usage from Metering; it is downstream to both.
  • Configuration & Feature Flags consumes Metadata and tenant overrides; it is downstream to both.
  • Usage & Metering consumes flag rules (for quota enforcement) and produces events upstream to Billing.
  • Audit & Compliance is cross-cutting: every other context emits audit records to it.
  • Notifications & Extensibility is downstream of all contexts that need user-facing communication or webhook projection.
  • AI Orchestration consumes read models and events from all other contexts, but never mutates them directly; it operates only via guarded contracts.

Context Ownership and Evolution

Each bounded context is aligned with a long-lived team. Teams own code, contracts, events, and SLOs. Ownership is defined as:

  • Core Contexts (Tenant, Identity, Metadata, Billing): platform backbone, must be stable, additive evolution only, versioned contracts.
  • Supporting Contexts (Config/Flags, Metering, Notifications, Audit): evolve frequently but within backward-compatible rules.
  • Innovative Contexts (AI Orchestration): intentionally fast-moving, isolated by guardrails and PR-based integration, so experiments never leak into authoritative contexts.

Contracts evolve under clear rules: additive changes are version bumps within the same stream; breaking changes require new topics or new API paths. Consumers are required to be idempotent and resilient to schema growth.


Multi-Tenancy & Tenant Management Context

Context Summary

Owns the lifecycle of organizations that consume the platform. Assigns authoritative tenantId, enforces regional residency and isolation, and coordinates first-time provisioning (defaults, edition wiring) without leaking external models into other contexts.

Ubiquitous Language

  • Tenant: An organizational account with contractual right to use ConnectSoft services.
  • Residency: Jurisdictional placement of a tenant’s data and compute (e.g., EU, US, IL). May imply storage/account partitioning.
  • Edition Defaults: The initial feature/flag configuration derived from a chosen product edition, applied at provisioning.
  • Lifecycle: The administrative state of a tenant (e.g., Provisioning, Active, Suspended, PendingDeletion, Deleted).

Aggregates / Entities / VOs / Enums

  • Aggregate Root
    • Tenant
      • Identity: TenantId (opaque ULID/GUID string)
      • State: LifecycleStatus, TenantProfile, TenantRegionResidency, Contacts[], EditionDefaultsRef, CreatedAtUtc, LockedAtUtc?
  • Entities
    • Contact (Id, Name, Email, Phone, Role: Owner, Billing, Technical)
  • Value Objects
    • TenantProfile (LegalName, DisplayName, BillingAccountRef?, Domain? (optional vanity), Tags[])
    • TenantRegionResidency (RegionCode, DataSiloId, ResidencyLocked: bool)
    • TenantScopedId (TenantId, LocalId) for downstream references
  • Enumerations
    • LifecycleStatus = Provisioning | Active | Suspended | PendingDeletion | Deleted
    • SuspensionReason = Billing | Abuse | AdminRequest | Security
    • DeletionReason = Requested | NonPayment | ToSViolation

Commands & Events

  • Commands (write-side)
    • ProvisionTenant(tenantProfile, residency, initialEditionRef, contacts[])
    • ActivateTenant(tenantId)
    • UpdateTenantProfile(tenantId, profilePatch)
    • AddOrUpdateContact(tenantId, contact)
    • LockResidency(tenantId) (required before ActivateTenant if policy demands)
    • SuspendTenant(tenantId, reason)
    • ReinstateTenant(tenantId)
    • RequestTenantDeletion(tenantId, reason) → transitions to PendingDeletion
    • FinalizeTenantDeletion(tenantId)Deleted (after data disposal workflow)
  • Domain Events (emitted by Tenant)
    • tenant.created
    • tenant.updated
    • tenant.residency_locked
    • tenant.activated
    • tenant.suspended
    • tenant.reinstated
    • tenant.deletion_requested
    • tenant.deleted
    • tenant.contact_added / tenant.contact_updated
    • tenant.edition_defaults_applied

All events include: eventId, occurredAtUtc, tenantId, aggregateVersion, payload, and correlation/causation IDs for orchestration.

Policies / Sagas

  • Residency Constraints Policy: Prevents activation unless residency is set and, if required by regulation, locked. Emits tenant.residency_locked once locked.
  • Edition Defaults Provisioning Policy: On tenant.created, fetches edition defaults from SaaS Core Metadata and applies them, emitting tenant.edition_defaults_applied. Failures trigger compensations and keep tenant in Provisioning.
  • RLS/Filters Enforcement Policy: Publishes tenantId and (optional) DataSiloId to identity/infra so storage and message paths are tenant-scoped by default.
  • Deletion Orchestrator Saga: On tenant.deletion_requested, coordinates: user offboarding, config teardown, billing closeout, data disposal/export window, then issues FinalizeTenantDeletion.

Repository & Read Model

  • Repository
    • ITenantRepository: Load/save by TenantId only. Optimistic concurrency via aggregate version.
    • No cross-tenant queries; repository boundary always requires tenantId.
  • Read Models
    • TenantDirectory (query-optimized view: status, region, editionRef, key contacts)
    • TenantProvisioningStatus (stepwise progress for operational dashboards)
    • ResidencyMap (tenant → data silos/partitions) for infra/config consumers

Contracts (APIs & Message Endpoints)

  • HTTP (admin plane)
    • POST /tenants → ProvisionTenant
    • POST /tenants/{id}:activate → ActivateTenant
    • POST /tenants/{id}:lock-residency → LockResidency
    • PATCH /tenants/{id} → UpdateTenantProfile
    • POST /tenants/{id}:suspend / POST /tenants/{id}:reinstate
    • DELETE /tenants/{id} → RequestTenantDeletion
    • POST /tenants/{id}:finalize-deletion
  • Events (integration plane)
    • Topics: tenants.domain.v1.* (domain events), tenants.ops.v1.* (operational signals)
    • Outbox → durable bus; at-least-once semantics; consumers must be idempotent

Invariants & State Machine

  • Identity & Uniqueness
    • TenantId is authoritative and immutable.
    • Optional uniqueness: TenantProfile.Domain must be globally unique if used for routing.
  • Residency
    • Must be set during provisioning; cannot change after ResidencyLocked=true.
    • If residency is jurisdiction-critical, locking is mandatory before activation.
  • Activation
    • Requires: residency set (and locked if policy), edition defaults applied successfully, minimum 1 Owner contact.
  • Suspension
    • Suspended tenants cannot receive new entitlements or issue new tokens for paid features; background jobs may be curtailed.
  • Deletion
    • PendingDeletion blocks new user sign-ups and de-scopes tokens; Deleted is terminal. No reactivation from Deleted.
  • State Machine
    • Provisioning → Active → (Suspended ↔ Active) → PendingDeletion → Deleted

Security & Compliance Notes

  • Authorization
    • Admin plane endpoints require platform admin scopes; tenant-scoped updates require TenantAdmin within the tenant.
  • Data Isolation
    • Every persistent and message path carries tenantId; read/write side enforce tenant scoping by default. Infra consumes ResidencyMap to place data in correct silo.
  • Audit
    • All commands produce audit records (who, when, why, deltas). Correlate with request/trace IDs.
  • PII Handling
    • Contacts and profile fields are classified; logs/events apply redaction where required.

Evolution & Versioning

  • Additive first: new optional profile fields and event attributes are backward-compatible.
  • Breaking changes create new event topics (.v2) or new API paths.
  • Residency catalogs evolve via configuration (not code). Transitions between catalogs never auto-migrate existing tenants—explicit migration workflows are required.

Identity (Authentication, Authorization, Federation)

Context Summary

Owns identities for people and workloads across tenants. Issues multi-tenant tokens enriched with roles, scopes, and edition-aware entitlements. Provides RBAC and ABAC evaluation, role lifecycle, external federation (OIDC/SAML/SCIM), and service principal management for automation and integrations.

Ubiquitous Language

  • User: A human identity authenticated via passwordless/OTP, password, or external IdP; belongs to one or more tenants with tenant-local roles.
  • Service Principal (SP): A non-human identity (app/integration) with client credentials, tenant-local permissions, and optional environment restrictions.
  • Role: A named set of permissions aligned with business capabilities (e.g., TenantAdmin, BillingAdmin, SupportAgent, Reader).
  • Scope: A permission atom exposed on APIs (e.g., billing.read, config.write), used for least-privilege tokens.
  • Entitlements: Effective feature rights derived from Edition and tenant overrides; included as claims for downstream enforcement.
  • Federation: Trust relationships with external IdPs (OIDC/SAML) and provisioning via SCIM.
  • Policy Decision: Result of evaluating RBAC + ABAC against a request context (actor, tenant, resource, action, attributes).

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • User
      • Identity: UserId
      • State: Emails[], PrimaryEmail, Status (Invited|Active|Disabled|Locked), TenantMemberships[], MFA settings, external identities (mapped IdP subjects)
    • ServicePrincipal
      • Identity: ServicePrincipalId
      • State: DisplayName, TenantBindings[], Credentials[] (client secrets/keys), Status
    • Role
      • Identity: RoleId
      • State: Name, Description, Scopes[], AssignableBy[] (role hierarchy), IsSystemRole
  • Entities
    • RoleAssignment (Id, Subject = User|SP, TenantId, RoleId, ExpiresAt?)
    • ExternalIdentity (IdP, SubjectId, Attributes snapshot for mapping)
  • Value Objects
    • UserEmail (Address, VerifiedAt?)
    • HashedSecret (Algorithm, Hash, CreatedAt, ExpiresAt?)
    • PolicyContext (ActorId, TenantId, Resource, Action, Attributes)
  • Enumerations
    • Scope (catalog of API operations)
    • Status (for User/SP lifecycle)

Commands & Events

  • Commands
    • InviteUser(tenantId, email, roles[])
    • RegisterUser(email, credentials?, externalIdentity?)
    • ActivateUser(userId) / DisableUser(userId) / LockUser(userId)
    • AssignRole(subjectId, tenantId, roleId, expiresAt?) / RevokeRole(...)
    • CreateServicePrincipal(tenantId, name, roles[])
    • RotateServicePrincipalSecret(spId)
    • ConfigureFederation(provider, settings) / EnableSCIM(provider) / SyncSCIMUser(...)
    • IssueToken(subjectId, tenantId, scopes[], audience, ttl)
  • Events
    • user.invited / user.registered / user.activated / user.disabled / user.locked
    • role.assigned / role.revoked
    • service_principal.created / service_principal.secret_rotated
    • federation.configured / scim.sync_performed
    • token.issued (audit stream; never contains raw secrets)

All events carry eventId, occurredAtUtc, tenantId? (where applicable), subjectId, aggregateVersion, payload, correlationId, causationId.

Policies / Sagas

  • RBAC Policy: Grants based on RoleAssignments at the tenant scope; role hierarchy controls who can assign what.
  • ABAC Policy: Evaluates request attributes (edition, entitlements, residency tags, resource owner) alongside RBAC for fine-grained decisions.
  • Edition-Aware Claims Policy: On token issuance, pulls effective entitlements from SaaS Core Metadata + Config overrides; embeds as claims.
  • Federation & SCIM Policy: Normalizes external identities, maps groups/claims to local roles, and provisions users via SCIM with least-privilege defaults.
  • Secret Rotation Policy: Enforces rotation cadence for SP credentials; emits service_principal.secret_rotated.

Repository & Read Model

  • Repositories
    • IUserRepository, IServicePrincipalRepository, IRoleRepository, IRoleAssignmentRepository
    • Load/save by Id; enforce optimistic concurrency.
  • Read Models
    • SubjectDirectory (user/SP summary by tenant)
    • EffectivePermissions (precomputed scopes/entitlements per subject+tenant)
    • FederationDirectory (providers, mappings, SCIM sync status)

Contracts (APIs & Token Schema)

  • HTTP (admin plane)
    • POST /tenants/{tenantId}/users:invite
    • POST /users:register
    • POST /tenants/{tenantId}/roles:assign
    • POST /tenants/{tenantId}/service-principals
    • POST /auth/token (Passwordless/PKCE/Client Credentials)
  • Token / Claim Schema (JWT)
    • sub: UserId or ServicePrincipalId
    • tid: TenantId (required for tenant-scoped tokens)
    • scp: array of Scope (least-privilege)
    • rol: tenant-local roles (optional)
    • ent: entitlements (feature flags/feature keys effective for this token)
    • edn: edition identifier bound at issue-time
    • res: residency tag/data silo hint (for downstream enforcement)
    • aud, iss, iat, exp, jti
    • Notes: No PII in tokens. Entitlements are hashed keys or short codes, not verbose configs.

Invariants & State Machine

  • A User must have at least one verified email before Active.
  • A Role cannot be deleted while assignments exist.
  • A ServicePrincipal must have at least one valid HashedSecret to issue tokens.
  • RoleAssignment is tenant-scoped; cross-tenant roles are separate assignments.
  • Token issuance requires: subject enabled, tenant exists and not suspended, scopes allowed by roles and policy, audience registered.

User State: Invited → Active ↔ Disabled/Locked Service Principal State: Created → Active ↔ Disabled Role Lifecycle: Defined → Published → (Deprecated) → Retired (no removal until zero assignments)

Security & Compliance Notes

  • Authentication: Supports passwordless (magic link/OTP), WebAuthn, OAuth2/OIDC, and client credentials for SPs.
  • Authorization: Evaluate RBAC first, then ABAC constraints. Deny by default; scopes cannot exceed role grants.
  • Key Management: Signing keys in HSM/KeyVault; secrets never logged. All sensitive fields use classification/redaction in logs/events.
  • Audit: Every admin change and token issuance is audited with correlation to upstream requests.
  • Tenant Isolation: Tokens always carry tid; downstream services must reject missing/foreign tenant contexts.

Federation & SCIM

  • Providers: OIDC and SAML with explicit mapping configs (claims → roles, groups → roles).
  • Just-in-Time (JIT) Provisioning: Optional; creates a disabled user pending admin approval or auto-assigns minimal roles per mapping policy.
  • SCIM 2.0: Supports user/group sync; external IDs tracked in ExternalIdentity.
  • Deprovisioning: SCIM group removal or IdP disable cascades to revoke roles and tokens.

Evolution & Versioning

  • Additive claim growth is allowed; breaking token changes introduce a new iss/aud pair or versioned discovery doc.
  • Role catalogs evolve via migration scripts and deprecation windows.
  • Federation mappings versioned per provider; SCIM schema extensions are additive-first.

SaaS Core Metadata Context (Products, Editions, Features, SLAs)

Context Summary

Authoritative source for product composition: products, editions, features, and SLAs. Defines what capabilities exist, how they bundle into editions, and which entitlements flow to downstream contexts. Aligns with pricing/billing and configuration by publishing a stable catalog and change events without leaking implementation details of other contexts.

Ubiquitous Language

  • Product: A marketable offering composed of one or more editions (e.g., “ConnectSoft Config Service”).
  • Edition: A curated bundle of features and quotas defining rights (e.g., Free, Standard, Enterprise).
  • Feature: A capability exposed to tenants and users (e.g., “Webhooks,” “Advanced Flags,” “SLA: 99.9%”).
  • Entitlement: The effective rights derived from Edition + overrides (keys/quotas/limits).
  • SLA (Service Level Agreement): Formal commitment of availability/support metrics and remedies.
  • Compatibility Rule: Constraint ensuring coherent edition evolution (e.g., Feature X requires Feature Y ≥ v2).
  • Deprecation: Timelined withdrawal of features/editions with migration guidance.

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • Product
      • Identity: ProductId
      • State: Name, Description, Status, DefaultEditionId?, PublishedVersion, Tags[]
    • Edition
      • Identity: EditionId
      • State: ProductId, Name, Description, Status, EditionFeatures[], SlaRef?, Version
    • Feature
      • Identity: FeatureId
      • State: Key (VO), Name, Description, Category, Status, DefaultQuota?, Version
    • ServiceLevelAgreement
      • Identity: SlaId
      • State: AvailabilityTarget, SupportWindow, ResponseTimes, CreditsPolicy, Version, Status
  • Entities
    • EditionFeature (link entity): FeatureId, Mode (Enabled|Conditional|Preview), Quota? (VO), Constraints[]
  • Value Objects
    • Key (canonical identifier for a feature used by downstream systems)
    • Quota (Dimension, Limit, ResetPeriod)
    • CompatibilityConstraint (RequiresFeatureKey, MinVersion?, MutuallyExclusiveWith?)
    • EntitlementDescriptor (FeatureKey, Mode, Quota?, Version)
  • Enumerations
    • Status = Draft | Published | Deprecated | Retired
    • ResetPeriod = PerRequest | Hour | Day | Month | BillingCycle

Commands & Events

  • Commands
    • CreateProduct(name, ...) / PublishProduct(productId, versionNotes?) / DeprecateProduct(productId, timeline)
    • CreateFeature(key, ...) / PublishFeature(featureId) / DeprecateFeature(featureId, timeline)
    • CreateEdition(productId, name, ...) / AddFeatureToEdition(editionId, featureId, mode, quota?) / RemoveFeatureFromEdition(...)
    • AttachSla(editionId, slaId) / UpdateSla(slaId, changes)
    • SetCompatibilityRules(editionId, constraints[])
  • Domain Events
    • product.published
    • feature.published
    • edition.published
    • edition.changed (features added/removed/updated)
    • sla.attached|updated
    • entitlements.changed (materialized entitlement set changed for an edition)
    • feature.deprecated / edition.deprecated / product.deprecated

Events include eventId, occurredAtUtc, aggregateId, version, and snapshots sufficient for downstream projections (no excessive verbosity).

Policies / Sagas

  • Edition Compatibility Policy: Validates that an edition’s features satisfy all CompatibilityConstraints before publication; blocks publish otherwise.
  • Default Feature Pack Policy: When a product sets a DefaultEditionId, ensure it is Published and minimally viable (contains mandated baseline features).
  • Deprecation Semantics Policy: On deprecation events, emit migration guidance and sunset dates; maintain Published read models until retirement date.
  • Entitlement Materialization Policy: On any edition.changed or feature.published/deprecated, recompute the edition’s EntitlementDescriptor[] and emit entitlements.changed for subscribers (Identity for claim enrichment, Config/Flags for evaluation defaults, Billing for SKU mapping).

Repository & Read Model

  • Repositories
    • IProductRepository, IEditionRepository, IFeatureRepository, ISlaRepository
    • Enforce optimistic concurrency; only aggregate-root persistence.
  • Read Models
    • CatalogView (Products → Editions → Features, statuses and versions)
    • EntitlementsView (Edition → EntitlementDescriptor[])
    • DeprecationCalendar (timeline per product/feature/edition)
    • SlaMatrix (targets and remedies per edition)

Contracts (APIs & Feeds)

  • HTTP (admin plane)
    • POST /products, POST /features, POST /products/{id}/editions
    • POST /editions/{id}:publish, POST /features/{id}:publish, POST /products/{id}:publish
    • POST /editions/{id}/features (add), DELETE /editions/{id}/features/{featureId} (remove)
    • POST /editions/{id}:attach-sla
    • POST /editions/{id}:set-compatibility
  • Query (public plane)
    • GET /catalog (paged); GET /products/{id}; GET /editions/{id}; GET /features/{id}
    • GET /editions/{id}/entitlements (stable published view)
  • Event Topics
    • catalog.domain.v1.* (domain events), catalog.entitlements.v1 (materialized entitlements)

Invariants & State Machine

  • A Feature Key is globally unique and immutable after publish.
  • An Edition cannot be Published if any included Feature is not Published or violates compatibility.
  • A Product cannot be Published without at least one Published edition.
  • Retired objects may not be referenced by new editions; existing references trigger migration flows.
  • ServiceLevelAgreement versions are immutable; updates create a new version and require explicit re-attachment.

Status Transitions: Draft → Published → Deprecated → Retired (monotonic; no revert to Draft after Published)

Security & Compliance Notes

  • Admin plane operations require elevated scopes; changes are fully audited with before/after diffs.
  • Feature keys and entitlements are non-PII; nevertheless, event payloads avoid leaking internal rationale—only necessary fields for consumers.
  • SLAs may include legal references; store references as metadata, not full legal text in events.

Evolution & Versioning

  • Additive-first: new features or quotas extend descriptors; consumers must ignore unknown fields.
  • Breaking changes: publish a new Feature.Version or Edition.Version; emit entitlements.changed with migration notes.
  • Sunset timelines are explicit in deprecation events; downstream contexts must honor deadlines in their policy engines.

Billing & Pricing Context (Plans, Subscriptions, Rating)

Context Summary

Authoritative model for commercial relationships with a tenant: plans/editions mapped to subscriptions, pricing models, rating/charging, invoices, credits, and promotions. Consumes entitlements from Catalog (Core Metadata) and usage from Metering to compute charges. Integrates with external payment providers through an adaptor ACL while preserving platform UL.

Ubiquitous Language

  • Plan: Commercial packaging that references a Product/Edition and a PricingModel (e.g., “Enterprise Monthly”).
  • Subscription: A tenant’s contractual enrollment to a Plan with lifecycle, term, and billing cycle.
  • Pricing Model: Rules that determine how charges are computed (flat, tiered usage, per-seat, hybrid).
  • Rating: The calculation process producing line items from entitlements, seats, and usage.
  • Invoice: A bill containing rated charges, taxes/credits/discounts, and payment status.
  • Charge: A captured payment transaction against an invoice balance.
  • Promotion: Time-bound discount or credit (coupon, introductory pricing).
  • Dunning: Automated recovery process for failed payments (retries, grace periods, suspension).

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • Subscription
      • Identity: SubscriptionId
      • State: TenantId, PlanId, EditionRef, Status, StartDate, CurrentPeriod { start, end }, Seats?, PromotionRefs[]
    • PricingModel
      • Identity: PricingModelId
      • State: Kind (Flat|PerSeat|TieredUsage|Hybrid), Currency, PriceComponents[], TaxesPolicyRef?
    • BillingRule
      • Identity: BillingRuleId
      • State: Mapping rules from entitlements/usage to billable dimensions; proration policy; overage policy
    • Invoice
      • Identity: InvoiceId
      • State: TenantId, SubscriptionId, Period, LineItems[], Subtotal, Taxes[], Adjustments[], Total, Currency, Status, DueDate, PaidAt?
  • Entities
    • LineItem (Description, Quantity, UnitPrice: Money, Amount: Money, Dimension?, Metadata)
    • PromotionApplication (PromotionId, Scope, Amount/Percent, Duration, AppliedFrom, AppliedUntil)
  • Value Objects
    • Money (Amount, Currency, arithmetic with rounding rules)
    • UsageThreshold (Dimension, FreeUnits, OverageRate)
    • ProrationPolicy (Strategy: Daily|Hourly|None)
    • Tier (From, To, UnitPrice)
    • SeatPolicy (MinSeats, MaxSeats?, IncludedSeats?)
  • Enumerations
    • BillingCycle = Monthly | Quarterly | Yearly
    • SubscriptionStatus = Trial | Active | PastDue | Suspended | Canceled | Expired
    • InvoiceStatus = Draft | Issued | Paid | PastDue | Void | Refunded

Commands & Events

  • Commands
    • CreatePlan(productId, editionRef, pricingModelId)
    • StartSubscription(tenantId, planId, seats?, startDate?, trialUntil?)
    • ChangeSubscription(subscriptionId, newPlanId|seatDelta|promotionCode, effectiveAt)
    • CancelSubscription(subscriptionId, when: Now|PeriodEnd)
    • GenerateInvoice(subscriptionId, period) / IssueInvoice(invoiceId)
    • RecordUsage(tenantId, subscriptionId, dimension, quantity, occurredAt) (write-through to Metering or internal buffer when needed)
    • CapturePayment(invoiceId, providerRef?) / RefundPayment(invoiceId, amount?)
    • ApplyPromotion(subscriptionId, promotionCode)
  • Events
    • subscription.started / subscription.activated
    • subscription.changed (plan/seats/promo/proration)
    • subscription.canceled
    • subscription.suspended / subscription.reinstated (dunning outcome)
    • invoice.generated / invoice.issued / invoice.paid / invoice.past_due / invoice.voided / invoice.refunded
    • charge.captured / charge.failed / charge.refunded
    • entitlements.updated (derived when plan/edition changes impact downstream claims)

All events include eventId, occurredAtUtc, tenantId, subscriptionId|invoiceId, aggregateVersion, and correlation/causation IDs.

Policies / Sagas

  • Edition-Linked Entitlements Policy: When a subscription starts/changes, resolve EditionRef from Catalog and emit entitlements.updated for Identity and Config/Flags.
  • Rating Policy: On GenerateInvoice, gather usage from Metering for the billing window, apply PricingModel and BillingRule (tiers, thresholds, overage), promotions, and taxes, then produce LineItems.
  • Proration Policy: On mid-period plan/seat changes, pro-rate charges per ProrationPolicy (daily/hourly) with credits where applicable.
  • Promotions Policy: Validate promotion eligibility and duration; apply to eligible components only (e.g., waive overage for first N days).
  • Dunning Policy: On charge.failed, schedule retries with backoff; manage grace period and transition to PastDue/Suspended; notify Notifications context.
  • Provider Adaptor ACL: Normalize payment operations across gateways (Stripe/Adyen/etc.) without leaking provider semantics into the domain.

Repository & Read Model

  • Repositories
    • ISubscriptionRepository, IPricingModelRepository, IBillingRuleRepository, IInvoiceRepository
    • Optimistic concurrency; aggregates persisted atomically with outbox entries.
  • Read Models
    • AccountBalance (running totals, credits)
    • InvoiceLedger (query-optimized invoices with payment summaries)
    • SubscriptionDirectory (status, plan, seats, next invoice date)
    • PricingCatalog (flattened price components for UI/config)

Contracts (APIs & Message Endpoints)

  • HTTP (admin/self-serve)
    • POST /tenants/{tenantId}/subscriptions
    • POST /subscriptions/{id}:change (plan/seats/promo)
    • POST /subscriptions/{id}:cancel
    • POST /subscriptions/{id}:generate-invoice
    • POST /invoices/{id}:capture / POST /invoices/{id}:refund
    • GET /tenants/{tenantId}/invoices?period=...
  • Events
    • billing.subscriptions.v1.* (domain)
    • billing.invoices.v1.* (invoice lifecycle)
    • billing.payments.v1.* (charges)
    • billing.entitlements.v1 (entitlements.updated for consumers)

Invariants & State Machine

  • A Subscription must reference a valid Plan (Product+Edition) and PricingModel.
  • Subscription.Active requires successful initial rating and (if not trial) a payable invoice or within grace period.
  • Invoice.Total equals sum of LineItems + Taxes + Adjustments; Total cannot be negative unless marked as Credit.
  • Currency must match across PricingModel, Invoice, and Charge.
  • Proration and promotions cannot reduce a line item below zero.
  • Subscription lifecycle: Trial → Active → (PastDue ↔ Active) → Suspended → (Reinstated → Active | Canceled | Expired)
  • Invoice lifecycle: Draft → Issued → (Paid | PastDue → (Void|Paid)) → Refunded

Security & Compliance Notes

  • No PANs/PCI data stored in-domain; provider tokens only. Secrets held in KeyVault/HSM.
  • All monetary values use Money with explicit rounding (banker’s rounding where required).
  • Audit includes rating inputs (usage snapshot hashes, price components) for reproducibility without exposing PII.
  • Tax/VAT handling via configurable TaxesPolicyRef; country rules externalized.

Evolution & Versioning

  • Additive-first pricing components and dimensions; unknown dimensions ignored by older consumers.
  • Breaking changes create new PricingModelId versions; existing subscriptions grandfathered until migration.
  • Provider adaptors are versioned behind ACL; switching providers does not change domain events or contracts.

Configuration & Feature Flags Context

Context Summary

Owns definition and evaluation of runtime flags and settings across product, edition, and tenant scopes. Provides a deterministic precedence model and low-latency evaluation APIs/SDKs. Integrates with Identity (claims, entitlements) and Catalog (feature keys) to compute effective capabilities without duplicating upstream models.

Ubiquitous Language

  • Flag: A boolean or multi-variant switch controlling behavior at runtime (e.g., webhooks.enabled).
  • Setting: A typed configuration value (string/number/json/datetime) used by services at runtime.
  • Scope: The level at which a rule applies—Product, Edition, Tenant.
  • Override: A tenant-scoped mutation that replaces or augments upstream defaults.
  • Evaluation Context: Attributes used to resolve a flag/setting (e.g., tenantId, edition, entitlements, userId, segment, region).
  • Kill Switch: A high-priority control that forces a flag off (or safe default) globally or per-scope.

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • FlagSet
      • Identity: FlagSetId (scope+ownerRef)
      • State: Flags[], Rules[], KillSwitches[], Version, UpdatedAtUtc
    • SettingSet
      • Identity: SettingSetId (scope+ownerRef)
      • State: Settings[] (typed), Schemas[] (validation), Version, UpdatedAtUtc
  • Entities
    • Override (Id, Target: TenantId, Items[] (Flag or Setting mutations), ActivatedAtUtc, ExpiresAtUtc?)
    • Rule (Targeting predicate, Rollout strategy, Value)
  • Value Objects
    • FlagKey (canonical dotted key, validated)
    • EvaluationContext (TenantId, Edition, Entitlements[], UserId?, Region?, Attributes map)
    • Rollout (Strategy: Fixed|Percentage|List, Seed, Buckets)
    • TypedValue (Type: Bool|String|Number|Json|Datetime, Value)
  • Enumerations
    • Scope = Product | Edition | Tenant
    • Precedence = KillSwitch > Tenant Override > Edition Default > Product Default

Commands & Events

  • Commands
    • DefineFlag(scope, ownerRef, key, defaultValue, rules?)
    • DefineSetting(scope, ownerRef, key, typedValue, schema?)
    • ApplyOverride(tenantId, items[], ttl?)
    • RemoveOverride(tenantId, keys[])
    • EnableKillSwitch(key, scope?, ownerRef?, reason, ttl?)
    • DisableKillSwitch(key, scope?, ownerRef?)
    • UpdateRules(scope, ownerRef, rules[])
  • Events
    • flag.changed (definition or rules updated)
    • override.applied / override.removed
    • killswitch.enabled / killswitch.disabled
    • setting.changed / schema.changed

Events include eventId, occurredAtUtc, tenantId?, key, version, correlation/causation IDs, and minimal diffs for efficient caches.

Policies / Sagas

  • Precedence Policy: Evaluator always applies KillSwitchTenant OverrideEdition DefaultProduct Default. Missing keys fall back to safe defaults or schema defaults.
  • Edition Alignment Policy: On Catalog entitlements.changed, synchronize edition defaults into FlagSet/SettingSet so evaluation reflects current editions.
  • Identity-Enriched Evaluation Policy: If EvaluationContext carries entitlements or roles, allow rules that target these attributes (e.g., enable preview for TenantAdmin only).
  • Staleness & TTL Policy: Overrides/kill switches with TTL auto-expire; emit removal events and update caches.
  • Change Propagation Policy: Any change publishes to an update stream for SDK streaming and server cache invalidation.

Repository & Read Model

  • Repositories
    • IFlagSetRepository, ISettingSetRepository, IOverrideRepository, IKillSwitchRepository
    • Optimistic concurrency via Version and ETags.
  • Read Models
    • EffectiveConfigView(tenantId) — a precomputed projection combining product/edition defaults with overrides and active kill switches.
    • KeyIndex — reverse index by FlagKey/SettingKey to impacted tenants/editions for fast invalidation.
    • AuditTrail — chronological changes for compliance and rollback assistance.

Evaluation & Caching Model

  • Server-Side Evaluation (authoritative):
    • Inputs: EvaluationContext
    • Steps: Resolve precedence chain → apply targeting rules/rollouts → return TypedValue with version and source.
    • Deterministic hashing for percentage rollouts using Seed + stable subject key (tenantId:userId?).
  • Caching Tiers:
    • L1 in-process: per-service memory cache keyed by tenantId + version.
    • L2 distributed: shared cache (e.g., Redis) storing EffectiveConfigView snapshots with ETag.
    • Streaming updates: subscribers invalidate L1 on flag.changed/override.applied events; L2 uses pub/sub for fine-grained key eviction.
  • Cold Start / Warmup:
    • On service boot, prefetch EffectiveConfigView for active tenants; lazy fill on miss with circuit breakers and fallback defaults.

Contracts (APIs & Message Endpoints)

  • HTTP (evaluation)
    • POST /config/evaluate{ key, context }{ value, type, source, version, evaluatedAt }
    • POST /config/evaluate/batch → bulk keys
  • HTTP (admin)
    • PUT /config/flags/{scope}/{ownerRef}/{key}
    • PUT /config/settings/{scope}/{ownerRef}/{key}
    • POST /config/overrides/{tenantId}
    • POST /config/killswitches/{key}:enable / POST ...:disable
  • SDKs
    • Server SDKs (C#/Node/Go) supporting cached evaluation, streaming updates, and context helpers.
  • Events
    • config.flags.v1.*, config.overrides.v1.*, config.killswitches.v1.*

Invariants & State Machine

  • FlagKey is globally unique within its scope and immutable once published.
  • A KillSwitch overrides any rule or override for the same key while active.
  • Override entries must pass schema validation for Setting keys.
  • Percentage rollouts must produce stable assignment per subject; changing the Seed intentionally reshuffles cohorts.
  • Edition defaults cannot reference unknown feature keys; alignment with Catalog is enforced before publish.

Security & Compliance Notes

  • Evaluation inputs may contain identifiers; classify and avoid logging raw EvaluationContext unless redacted.
  • Admin mutations require elevated scopes; all changes are audited with before/after diffs.
  • No PII in events; tenant identifiers are included strictly for routing/invalidation.

Evolution & Versioning

  • Additive-first: new flags/settings and new rule predicates co-exist; older SDKs ignore unknown rule types.
  • Breaking schema changes require new keys or versioned namespaces (e.g., featureX.v2.enabled).
  • Deprecations emit warnings and provide migration hints in admin APIs; sunset dates tracked in AuditTrail.

Usage & Metering Context

Context Summary

Owns intake, normalization, and aggregation of usage signals across tenants. Produces authoritative counters for real-time quota/rate enforcement and billing handoff. Designed as a high-throughput write path with append-only semantics, anti-tamper protections, and resilient late/duplicate event handling.

Ubiquitous Language

  • Usage Event: An immutable record of activity against a billable/limited dimension (e.g., api_calls, messages_tokens, webhooks_delivered).
  • Dimension: The named metric being measured; bound to Catalog feature keys and Billing dimensions.
  • Window: A rolling or fixed period over which usage is accumulated (e.g., per-minute, per-day, per-billing-cycle).
  • Counter: The aggregated usage value for a {tenantId, dimension, window}.
  • Quota Policy: Limits and behaviors (soft/hard) for a dimension (e.g., 1M calls/month, burst 100 rps).
  • Backfill: Import of historical usage for migration or reconciliation.

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • UsageMeter
      • Identity: {tenantId, dimension}
      • State: Counters[] by Window, LastIngestedAt, ReplayCursor, integrity hashes
    • QuotaPolicy
      • Identity: {dimension, plan|edition}
      • State: Window, Limit, Burst?, OveragePolicy?, Enforcement (Soft|Hard), GracePeriod?
  • Entities
    • Counter (WindowId, Value, ResetAtUtc, UpdatedAtUtc)
  • Value Objects
    • Dimension (Key, Unit, Normalization rules)
    • Window (Kind: Fixed|Rolling, Size, Anchor)
    • UsageRecord (OccurredAtUtc, Quantity, Source, IdempotencyKey, Signature?, Metadata)
  • Enumerations
    • Enforcement = Soft | Hard
    • OveragePolicy = Bill | Throttle | Block | AlertOnly

Commands & Events

  • Commands
    • RecordUsage(tenantId, dimension, quantity, occurredAtUtc, idempotencyKey, metadata?)
    • BackfillUsage(tenantId, dimension, records[]) (privileged)
    • DefineQuotaPolicy(dimension, planOrEdition, limit, window, enforcement, overagePolicy?)
    • ResetWindow(tenantId, dimension, windowId) (scheduled for fixed windows)
  • Events
    • usage.recorded (normalized, deduped)
    • usage.aggregated (counter updated)
    • quota.nearing_limit (threshold crossed, e.g., 80%)
    • quota.exceeded (limit crossed)
    • usage.backfilled (reconciliation outcome)

All events carry eventId, occurredAtUtc, tenantId, dimension, windowId?, quantity|counterValue, and correlation/causation IDs.

Policies / Sagas

  • Edition-Aware Limits Policy: When Catalog/Config emits entitlements.changed, recompute effective QuotaPolicy per tenant+dimension.
  • Anti-Tamper Policy: Validate source signatures (when present), enforce monotonic counters, and reject negative or impossible deltas outside backfill mode.
  • Late Event Handling Policy: Accept late usage within a tolerant window; adjust the correct historical counter and emit compensating usage.aggregated. Very late events route to reconciliation.
  • Idempotency Policy: Deduplicate by (tenantId, dimension, idempotencyKey); safe to retry at-least-once deliveries.
  • Threshold Notification Policy: Emit quota.nearing_limit at configured thresholds; notify Notifications context and expose advisory headers in evaluation APIs.
  • Overage/Enforcement Policy: On quota.exceeded, apply OveragePolicy (emit billable overage, throttle, or block via Config/Identity integration signals).

Repository & Read Model

  • Write Path
    • Append-only usage log (partitioned by tenantId and dimension), persisted with an outbox for downstream aggregation.
  • Repositories
    • IUsageMeterRepository, IQuotaPolicyRepository
  • Read Models
    • RealTimeCounters (hot cache of {tenantId, dimension, currentWindows} for enforcement)
    • BillingWindowTotals (period totals aligned with billing cycles)
    • UsageTimeline (time-bucketed series for analytics/dashboards)

Contracts (APIs & Streams)

  • HTTP (ingest)
    • POST /metering/usage → accepts single/batch UsageRecord with idempotency support
    • POST /metering/backfill (privileged)
  • HTTP (query)
    • GET /metering/counters?tenantId=&dimension=&window=
    • GET /metering/limits?tenantId= (effective QuotaPolicy view)
  • Streams
    • metering.usage.v1 (post-normalization)
    • metering.counters.v1 (aggregations)
    • metering.quota.v1 (nearing_limit, exceeded)

Ingestion & Aggregation Model

  • Normalization: Convert provider-specific metrics to canonical Dimension units; reject unknown dimensions.
  • Bucketing: Fixed windows (e.g., day, month) use anchored buckets; rolling windows maintain sliding aggregates with decay queues or ring buffers.
  • Performance: Batched aggregation with atomic counter updates; L1 in-memory + L2 distributed caches.
  • Reconciliation: Periodic job compares append-only log to counters; repairs drift and re-emits usage.aggregated if needed.

Invariants & State Machine

  • Counters are non-decreasing within an open window; reset only when the window elapses or during authorized backfill correction.
  • Each UsageRecord must have a non-empty idempotencyKey in online ingest paths.
  • Dimensions used in quotas must exist in Catalog/Config mapping.
  • QuotaPolicy must specify a Window; enforcement without a window is invalid.

Security & Compliance Notes

  • Ingest endpoints rate-limited and require scoped credentials; sensitive metadata is classified and redacted in logs.
  • Multi-tenant scoping enforced on write and read; no cross-tenant queries without platform-admin scope.
  • Audit trail of all ingested records and adjustments with hashes for integrity verification.

Evolution & Versioning

  • New dimensions added additively with mapping tables; old consumers ignore unknown dimensions.
  • Changes to window definitions are versioned; overlapping windows may coexist during migration.
  • Backfill and reconciliation tools are versioned separately and run in controlled maintenance windows.

Audit & Compliance Context

Context Summary

Owns the immutable, append-only record of actor/resource changes, administrative operations, policy decisions (authz, data access), and system events across the platform. Provides queryable views, retention enforcement, legal hold, and eDiscovery/export workflows. Applies classification and redaction at write and read time to minimize exposure of sensitive data while preserving investigative value and regulatory traceability.

Ubiquitous Language

  • Audit Stream: An append-only ledger of AuditRecord entries partitioned by tenant and category.
  • Audit Record: A fact describing “who did what to which resource, when, from where,” including decision context (allow/deny), deltas, and correlation IDs.
  • Data Class: The sensitivity level of fields involved (e.g., Public, Internal, Confidential, PII, Secret).
  • Retention Policy: Rules governing how long records persist before disposal and exceptions (e.g., legal hold).
  • Legal Hold: A suspension of deletion to preserve evidence for investigation or litigation.
  • WORM Storage: Write-Once-Read-Many semantics to ensure immutability within the retention window.

Aggregates / Entities / VOs / Enums

  • Aggregate Root
    • AuditStream
      • Identity: {tenantId, streamId} (streams by category: authz, admin, config, billing, data_access, etc.)
      • State: RetentionPolicy, LegalHolds[], LastSequence, IntegrityAnchor (hash chain root)
  • Entities
    • AuditRecord
      • Fields: sequence, occurredAtUtc, actor (subject, type, tenant scope), action, resource (type, id), decision (if policy), result (Success|Failure|Denied), deltas?, ip|userAgent?, dataClasses[], redactionHints[], correlationId, causationId, hash, prevHash
  • Value Objects
    • RetentionPolicy (Duration, GraceDays, DeletionMode: Scheduled|Manual, ExportBeforeDelete: bool)
    • ClassificationRule (FieldPath, Class: DataClass, Redaction: Mask|Erase|Hash|None)
    • ExportDescriptor (Format: Parquet|NDJSON|CSV, Range, Filters, Manifest)
  • Enumerations
    • DataClass = Public | Internal | Confidential | PII | Secret
    • Decision = Allow | Deny | NotApplicable | Indeterminate

Commands & Events

  • Commands
    • AppendAuditRecord(streamId, record) (only way to write)
    • ConfigureRetention(streamId, retentionPolicy)
    • PlaceLegalHold(streamId, reason, placedBy, until?)
    • ReleaseLegalHold(streamId, reason, releasedBy)
    • StartExport(streamId, descriptor) / FinalizeExport(exportId)
    • PurgeByRetention(streamId, cutoffDate) (system-initiated)
  • Events
    • audit.record_appended
    • audit.retention_configured
    • audit.legal_hold_placed / audit.legal_hold_released
    • audit.export_started / audit.export_finalized
    • audit.purged_by_retention

All entries/events include tenant scoping, sequence, and integrity metadata (hash, prevHash) to enable tamper-evidence.

Policies / Sagas

  • Retention Enforcement Policy: Enforces default 7-year retention (tenant-configurable within allowed bounds). Schedules purge tasks that respect legal holds and export-before-delete settings.
  • PII Minimization & Redaction Policy: Applies ClassificationRules at append time; sensitive fields are masked/hashed/erased in stored deltas and metadata while retaining enough context for forensics. Read-time redaction layers ensure only authorized viewers see minimally necessary data.
  • Decision Logging Policy: When Identity/Config/Billing make allow/deny decisions, an AuditRecord is appended with the evaluated attributes and outcome (never raw secrets).
  • Export Packaging Policy: Builds verifiable export bundles (manifest + checksums + optional encryption) for regulators and investigations; supports pagination and resumable exports.
  • Integrity Chain Policy: Maintains a per-stream hash chain; periodically publishes an IntegrityAnchor snapshot for external timestamping to strengthen non-repudiation.

Repository & Read Model

  • Write Path
    • Append-only ledger optimized for sequential writes; WORM-capable storage tier with time-based partitioning by tenant and month.
  • Repositories
    • IAuditStreamRepository for metadata/anchors
    • IAuditAppender for atomic appends with hash-chain updates
  • Read Models
    • AuditTimeline (time-ordered query with filters: actor, action, resource, decision, data class)
    • PolicyDecisionLog (subset optimized for authorization/compliance analytics)
    • ExportCatalog (history of exports, manifests, checksums, recipients)

Contracts (APIs & Access Patterns)

  • HTTP (append)
    • POST /audit/streams/{streamId}/records (internal only; authenticated service principals)
  • HTTP (query)
    • GET /audit/streams/{streamId}/records?from=...&to=...&actor=...&action=...&resource=...&decision=...&class=...
    • GET /audit/streams/{streamId}/integrity (returns anchors and verification instructions)
  • HTTP (admin)
    • PUT /audit/streams/{streamId}/retention
    • POST /audit/streams/{streamId}:legal-hold
    • DELETE /audit/streams/{streamId}:legal-hold
    • POST /audit/streams/{streamId}:export
  • Streams
    • audit.events.v1.* for meta-events (holds, retention changes, exports)

Invariants & State Machine

  • AuditRecord.sequence is strictly increasing within a stream; gaps are invalid.
  • hash = H(record || prevHash); changing any stored record invalidates chain verification.
  • Retention purges never run on streams with active legal holds.
  • Append operations must include correlationId for cross-context traceability.
  • Classification must be resolved before persist; records lacking classification are rejected.

Security & Compliance Notes

  • Access to query/export is role-gated (ComplianceOfficer, SecurityAnalyst, TenantAdmin (limited)); fine-grained ABAC filters enforce tenant and purpose restrictions.
  • Exports can be encrypted with customer-managed keys; access is logged and watermarked.
  • No raw credentials, tokens, or PANs may be stored; use one-way hashes or redacted placeholders.
  • DSAR support: queries can be filtered by data subject identifiers to facilitate subject access and deletion (where legally permitted, not overriding audit retention obligations).

Evolution & Versioning

  • Schema evolves additively; new fields default to redacted unless classified.
  • Retention defaults may change across policy versions; each stream records the RetentionPolicy.Version.
  • Integrity scheme upgrades (e.g., switching hash algorithms) are versioned; anchors include algorithm metadata with migration procedures.

Notifications & Extensibility Context (Email/SMS/Webhooks)

Context Summary

Provides tenant-scoped communications (email/SMS/push) and outbound extensibility via signed webhooks. Normalizes provider differences behind an adaptor ACL, enforces branding and rate limits, and guarantees at-least-once delivery with idempotent receivers. Integrates with other contexts to announce lifecycle events, billing changes, quota thresholds, and administrative actions.

Ubiquitous Language

  • Notification Template: Parameterized content with localization, channels, and tenant branding.
  • Channel Config: Tenant’s provider settings and policies per channel (email, SMS, push).
  • Dispatch: A request to send a message to one or more recipients with a template and data model.
  • Delivery Status: The state machine for a message (Queued, Sent, Delivered, Bounced, Failed).
  • Webhook Endpoint: A tenant-registered HTTP callback target with signing configuration and replay protection.
  • Preference: Recipient’s subscription/consent for categories (e.g., billing, system alerts, marketing).

Aggregates / Entities / VOs / Enums

  • Aggregate Roots

  • NotificationTemplate

    • Identity: TemplateId
    • State: Name, ChannelKinds[], Localization[], Category, Placeholders[], Version, Status
    • ChannelConfig

    • Identity: {tenantId, channelKind}

    • State: ProviderRef, CredentialsRef, RateLimit, CategoryPolicies[], DefaultFrom/Branding
    • WebhookEndpoint

    • Identity: EndpointId

    • State: TenantId, Url, Secret(s), SignatureSpec (algo, header names), TimestampsTolerance, Enabled, ReplayWindow
    • Entities
  • DispatchRequest (Id, TemplateId, TenantId, Recipients[], Data, Category, CorrelationId)

  • Message (Id, Channel, RenderedBody, Headers, DeliveryStatus, ProviderMessageId?, Attempts, LastError?)
  • SecretVersion (KeyId, CreatedAt, ExpiresAt?, State)
  • Value Objects

  • Branding (LogoUrl, Colors, Footer, FromName/Email/SMS SenderId, Domain)

  • RateLimit (Window, Max)
  • Signature (Algorithm: HMAC-SHA256|Ed25519, KeyId)
  • Enumerations

  • DeliveryStatus = Queued | Sent | Delivered | Bounced | Failed | Suppressed

Commands & Events

  • Commands

  • CreateTemplate(...) / PublishTemplate(templateId)

  • ConfigureChannel(tenantId, channelKind, config)
  • Send(dispatchRequest) (may be batch)
  • Retry(messageId) (policy-driven backoff)
  • RegisterWebhook(tenantId, url, spec) / RotateWebhookSecret(endpointId) / DisableWebhook(endpointId)
  • RecordPreference(subjectId, category, allowed)
  • Events

  • message.queued / message.sent / message.delivered / message.bounced / message.failed / message.suppressed

  • webhook.signed / webhook.delivered / webhook.failed
  • template.published / channel.configured / webhook.registered|disabled|rotated
  • preference.changed

Events carry tenantId, messageId|endpointId, category, correlationId, and provider metadata (minimal, non-PII).

Policies / Sagas

  • Tenant Branding Policy: On Send, merge tenant Branding with template; ensure DMARC/SPF alignment for email domains where applicable.
  • Rate Limit Policy: Enforce RateLimit per tenant+channel; queue overflow with backoff; emit message.suppressed if policy requires.
  • Provider Adaptor ACL: Abstract provider specifics (SendGrid/Mailgun/Twilio/FCM/etc.). Normalize errors and receipts; never leak provider tokens in events.
  • Preference & Compliance Policy: Honor user/category preferences and legal constraints (CAN-SPAM/GDPR). Marketing categories require explicit consent; system-critical may bypass with audit.
  • Retry & DLQ Policy: Exponential backoff; transition to DLQ after N attempts; surface DLQ metrics for ops. Idempotent resend protected by messageId.
  • Webhook Signature & Replay Policy: Sign payload with KeyId + Signature; include timestamp and nonce. Receivers must validate HMAC/Ed25519, check freshness within TimestampsTolerance, and dedupe by nonce.

Repository & Read Model

  • Repositories

  • ITemplateRepository, IChannelConfigRepository, IWebhookRepository, IDispatchRepository, IMessageRepository, IPreferenceRepository

  • Read Models

  • MessageOutbox (pending sends and attempts)

  • DeliveryLog (status timelines per message/recipient)
  • WebhookDeliveryLog (attempts, signatures, latencies)
  • PreferenceDirectory (effective opt-ins per subject/category)

Contracts (APIs & Message Endpoints)

  • HTTP (admin)

  • POST /tenants/{tenantId}/templates / POST /templates/{id}:publish

  • PUT /tenants/{tenantId}/channels/{channelKind}
  • POST /tenants/{tenantId}/webhooks / POST /webhooks/{id}:rotate / POST /webhooks/{id}:disable
  • PUT /preferences/{subjectId}/{category} (consent)
  • HTTP (send)

  • POST /notifications:send → accepts DispatchRequest (batch supported)

  • Events to external receivers (webhooks)

  • Headers: X-CS-Signature, X-CS-KeyId, X-CS-Timestamp, X-CS-Nonce

  • Body: event name, version, tenant, payload, correlationId
  • Inbound Provider Webhooks

  • Provider-specific endpoints behind ACL for delivery/bounce receipts, mapped to message.* events

Invariants & State Machine

  • A DispatchRequest must reference a Published template and a configured channel.
  • Messages are immutable after Sent; only DeliveryStatus transitions change.
  • Webhook endpoints require at least one active SecretVersion; rotation retains previous key for overlap window.
  • Replay windows reject duplicate (endpointId, nonce) within ReplayWindow.
  • Suppressed messages (due to preferences or policy) never enter provider pipelines.

Message lifecycle: Queued → Sent → (Delivered | Bounced | Failed) with policy-driven retries. Webhook lifecycle: Signed → Delivered | Failed with retries until max attempts/DLQ.

Security & Compliance Notes

  • Secrets and provider credentials stored in HSM/KeyVault; access by least privilege.
  • PII minimization: templates support placeholder filtering/redaction; logs store only necessary metadata.
  • All sends and preference changes are audited with correlation to upstream requests.
  • Link tracking (if enabled) uses privacy-preserving redirects and respects Do-Not-Track/preferences.

Evolution & Versioning

  • Templates are versioned; changes require PublishTemplate to take effect.
  • Webhook signature specs are versioned per endpoint; receivers can rotate to new schemes without downtime.
  • New channels/providers added behind ACL without impacting domain contracts.

AI Orchestration Context (Optional but First-Class)

Context Summary

Provides agentic workflows that read from authoritative domains, synthesize artifacts (docs, PRs, scripts), and propose changes via controlled interfaces. Never writes directly to domain aggregates. All mutations occur through PR-based processes, with explicit human or policy-based approval. Ensures least-privilege tool grants, full traceability, and safe rollback.

Ubiquitous Language

  • Automation Flow: A structured, auditable sequence of agent steps that accomplish a goal (e.g., “Generate ADR + PR for a new quota policy”).
  • Artifact: A concrete output produced by a step (markdown spec, migration script, test suite, PR diff).
  • Tool: A callable capability (read-only domain query, doc generator, code scaffold, test executor, PR creator).
  • Tool Grant: A scoped, time-limited permission for a flow to call specific tools with bounded inputs/tenants.
  • Guardrail Policy: Constraints defining what the flow may read, generate, and propose; includes reviewers, CI checks, and redaction rules.
  • Run Log: An append-only trace of prompts, tool calls (parameters/outputs hashes), decisions, and artifacts.

Aggregates / Entities / VOs / Enums

  • Aggregate Roots
    • AutomationFlow
      • Identity: FlowId
      • State: Goal, Initiator (human/system), Status (Planned|Running|AwaitingReview|Completed|Aborted|Failed), ToolGrants[], GuardrailPolicyRef, RunLogRef, Artifacts[]
    • ArtifactBundle
      • Identity: ArtifactBundleId
      • State: Artifacts[] (typed), Checks[] (lint/tests/security), ProposedPR?, IntegrityHash, Provenance
  • Entities
    • RunLog (ordered entries with timestamps, step kind, tool call digests, decision notes, redaction markers)
    • ProposedPR (Repo, Branch, DiffSummary, Linked Work Items, Reviewers, Status)
  • Value Objects
    • ToolGrant (ToolId, AllowedOperations, Scope: tenants/contexts, InputSizeLimits, ExpiresAt)
    • GuardrailPolicy (RequiredChecks, ReviewerPolicy, SensitiveDataRules, AllowedRepos, AllowedBranches, MaxChangeSize, PRTitleTemplate)
    • ArtifactRef (Type: ADR|HLD|Code|Migration|Test|Dataset, URI, SHA256)
  • Enumerations
    • FlowStatus = Planned | Running | AwaitingReview | Completed | Aborted | Failed
    • ArtifactType = ADR | HLD | Code | Migration | Test | Dataset | Diagram

Commands & Events

  • Commands
    • StartFlow(goal, initiator, policyRef, initialGrants[])
    • GrantTool(flowId, toolGrant) / RevokeTool(flowId, toolId)
    • ProduceArtifact(flowId, artifactType, contentRef) (write to ArtifactStore; record digest)
    • OpenProposedPR(flowId, artifactBundleId) / UpdateProposedPR(flowId, diffRef)
    • RequestReview(flowId) / AbortFlow(flowId, reason) / CompleteFlow(flowId)
  • Events
    • flow.started
    • artifact.generated
    • tool.granted / tool.revoked
    • pr.opened / pr.updated / pr.checks_passed / pr.checks_failed / pr.merged / pr.closed
    • flow.completed / flow.aborted / flow.failed

All events include flowId, initiator, policyVersion, artifact digests where applicable, and correlation IDs to external CI systems.

Policies / Sagas

  • Least-Privilege Tool Use: A flow cannot call a tool without an active ToolGrant. Grants expire and are scoped to tenants/contexts. Requests for escalation are routed to reviewers with rationale.
  • Auditable Traces: Every prompt, tool input/output digest, and artifact hash is recorded in RunLog. Sensitive content is redacted per GuardrailPolicy.
  • PR-Only Mutations: Any proposed domain change must be expressed as a PR. Direct writes to aggregates are forbidden; CI checks (lint/tests/security/architecture rules) gate merge.
  • Reviewer & Check Gate: RequestReview enforces reviewer quorum and checklists (e.g., privacy review for telemetry additions). Automatic merges are allowed only if policy permits.
  • Rollbacks & Safety: On pr.checks_failed or flow.failed, the saga rolls back ephemeral resources (branches, temp environments) and notifies owners.
  • Artifact Quality Policy: Generated artifacts must pass format validators (ADR schema, HLD lint, code style). Failing artifacts are rejected from bundling.

Repository & Read Model

  • Repositories
    • IAutomationFlowRepository, IArtifactBundleRepository, IRunLogRepository, IToolGrantRepository, IGuardrailPolicyRepository
  • Read Models
    • FlowDashboard (status, owners, outstanding reviews, age)
    • ArtifactIndex (by tenant/context/type; deduplicated via hash)
    • PolicyMatrix (who can grant which tools; default timeouts)
    • PRStatusBoard (per repo/branch: checks, reviewers, merge SLA)

Contracts (APIs & Integrations)

  • HTTP (orchestration)
    • POST /ai/flows (start)
    • POST /ai/flows/{id}:grant-tool
    • POST /ai/flows/{id}:produce-artifact
    • POST /ai/flows/{id}:open-pr
    • POST /ai/flows/{id}:request-review
    • POST /ai/flows/{id}:abort / POST /ai/flows/{id}:complete
  • Tooling Interfaces
    • Read-only domain adapters: Catalog, Config, Billing, Metering, Audit (query endpoints only)
    • DevOps adapters: Git (repos/branches/PRs), CI (pipelines/results), Issue Trackers (work items)
    • Document adapters: ADR/HLD repositories with schema validators
  • Event Topics
    • ai.flows.v1.*, ai.artifacts.v1.*, ai.prs.v1.*

Invariants & State Machine

  • A flow cannot transition to AwaitingReview without at least one ArtifactBundle or ProposedPR.
  • ToolGrant.ExpiresAt must be in the future at use time; expired grants cause the step to fail closed.
  • Artifact digests are immutable; any modification creates a new artifact entry and updates the bundle.
  • Flows that open PRs must attach GuardrailPolicy snapshot and CI configuration IDs to ensure reproducible checks.
  • Lifecycle: Planned → Running → (AwaitingReview → Completed | Failed | Aborted)

Security & Compliance Notes

  • Tools operate under service principals with scoped tokens; no human tokens are stored.
  • Redaction on prompts/outputs per GuardrailPolicy with classification tags; RunLog stores hashes for large outputs and retains only necessary snippets.
  • All PRs reference work items and include a minimal data-exposure assessment if changes touch logging/telemetry or PII-bearing paths.
  • Egress controls: flows cannot call arbitrary endpoints; only allowlisted adapters and repositories.

Evolution & Versioning

  • GuardrailPolicy is versioned; each flow records the policy version applied.
  • Tool interfaces are additive-first; breaking changes require a new ToolId/version and migration helpers.
  • PR templates and check suites are referenced by versioned IDs to ensure stable historical verification.

Patterns Cookbook & Reference Implementations

Aggregate Template

// Domain: Shared kernel
public abstract class AggregateRoot<TId>
{
    private readonly List<IDomainEvent> _uncommitted = new();
    public TId Id { get; protected set; } = default!;
    public int Version { get; private set; } = 0;

    protected void Ensure(bool condition, string message)
    {
        if (!condition) throw new DomainRuleViolationException(message);
    }

    protected void Emit(IDomainEvent @event)
    {
        Apply(@event);
        _uncommitted.Add(@event);
        Version++;
    }

    protected abstract void Apply(IDomainEvent @event);

    public IReadOnlyCollection<IDomainEvent> DequeueUncommitted() {
        var copy = _uncommitted.ToArray();
        _uncommitted.Clear();
        return copy;
    }
}

public sealed class DomainRuleViolationException : Exception
{
    public DomainRuleViolationException(string message) : base(message) { }
}

Usage pattern

public sealed class Subscription : AggregateRoot<string>
{
    public string TenantId { get; private set; } = default!;
    public string PlanId { get; private set; } = default!;
    public SubscriptionStatus Status { get; private set; } = SubscriptionStatus.Trial;

    private Subscription() { }

    public static Subscription Start(string id, string tenantId, string planId)
    {
        var agg = new Subscription();
        agg.Emit(new SubscriptionStarted(id, tenantId, planId));
        return agg;
    }

    protected override void Apply(IDomainEvent e)
    {
        switch (e)
        {
            case SubscriptionStarted s:
                Id = s.SubscriptionId;
                TenantId = s.TenantId;
                PlanId = s.PlanId;
                Status = SubscriptionStatus.Trial;
                break;

            case SubscriptionActivated:
                Status = SubscriptionStatus.Active;
                break;

            case SubscriptionCanceled:
                Status = SubscriptionStatus.Canceled;
                break;
        }
    }

    public void Activate()
    {
        Ensure(Status is SubscriptionStatus.Trial or SubscriptionStatus.PastDue,
            "Only trial or past-due subscriptions can be activated.");
        Emit(new SubscriptionActivated(Id));
    }

    public void Cancel()
    {
        Ensure(Status is not SubscriptionStatus.Canceled, "Already canceled.");
        Emit(new SubscriptionCanceled(Id));
    }
}

Value Objects

public abstract record ValueObject;

public sealed record Money(decimal Amount, string Currency) : ValueObject
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new InvalidOperationException("Currency mismatch.");
        return this with { Amount = Amount + other.Amount };
    }
}

Smart Enumerations

public abstract class SmartEnum<T> where T : SmartEnum<T>
{
    public string Name { get; }
    public int Value { get; }

    protected SmartEnum(string name, int value) { Name = name; Value = value; }

    public override string ToString() => Name;

    private static readonly Dictionary<int, T> _byValue = new();
    private static readonly Dictionary<string, T> _byName = new(StringComparer.OrdinalIgnoreCase);

    protected static void Register(T item)
    {
        _byValue[item.Value] = item;
        _byName[item.Name] = item;
    }

    public static T FromName(string name) => _byName[name];
    public static T FromValue(int value) => _byValue[value];
}

public sealed class BillingCycle : SmartEnum<BillingCycle>
{
    public static readonly BillingCycle Monthly = new(nameof(Monthly), 1);
    public static readonly BillingCycle Quarterly = new(nameof(Quarterly), 3);
    public static readonly BillingCycle Yearly = new(nameof(Yearly), 12);

    private BillingCycle(string name, int value) : base(name, value) => Register(this);
}

Domain Events & Versioning

public interface IDomainEvent
{
    Guid EventId { get; }
    DateTime OccurredAtUtc { get; }
    string AggregateId { get; }
    int AggregateVersion { get; }
    string EventType { get; }         // e.g., "subscription.started"
    string SchemaVersion { get; }     // e.g., "v1"
}

public abstract record DomainEventBase(
    Guid EventId,
    DateTime OccurredAtUtc,
    string AggregateId,
    int AggregateVersion,
    string EventType,
    string SchemaVersion = "v1") : IDomainEvent;

public sealed record SubscriptionStarted(string SubscriptionId, string TenantId, string PlanId)
    : DomainEventBase(Guid.NewGuid(), DateTime.UtcNow, SubscriptionId, 0, "subscription.started", "v1");

public sealed record SubscriptionActivated(string SubscriptionId)
    : DomainEventBase(Guid.NewGuid(), DateTime.UtcNow, SubscriptionId, 0, "subscription.activated", "v1");

public sealed record SubscriptionCanceled(string SubscriptionId)
    : DomainEventBase(Guid.NewGuid(), DateTime.UtcNow, SubscriptionId, 0, "subscription.canceled", "v1");

Event versioning guidance

  • Prefer additive schema evolution inside the same SchemaVersion.
  • Breaking changes → publish a new SchemaVersion and/or new event type suffix (e.g., .v2 topic).
  • Consumers must be idempotent and ignore unknown fields.

Outbox / Inbox (Idempotency, Retries)

// Infrastructure sketch
public sealed class OutboxMessage
{
    public long Id { get; init; }
    public string AggregateId { get; init; } = default!;
    public string Topic { get; init; } = default!;
    public string PayloadJson { get; init; } = default!;
    public DateTime OccurredAtUtc { get; init; }
    public int Attempt { get; set; }
    public string? LastError { get; set; }
}

public interface IOutbox
{
    Task EnqueueAsync(IEnumerable<IDomainEvent> events, CancellationToken ct);
    Task<IReadOnlyList<OutboxMessage>> DequeueBatchAsync(int max, CancellationToken ct);
    Task MarkSucceededAsync(long id, CancellationToken ct);
    Task MarkFailedAsync(long id, string error, CancellationToken ct);
}

// Inbox side
public sealed class InboxDeduper
{
    private readonly IInboxStore _store; // persists (consumerGroup, messageId) → processedAt

    public async Task<bool> TryBeginAsync(string consumerGroup, string messageId, CancellationToken ct)
    {
        // Returns false when duplicate
        return await _store.TryInsertAsync(consumerGroup, messageId, DateTime.UtcNow, ct);
    }
}

Retry strategy

  • Exponential backoff with jitter (base^attempt + random(0..delta)).
  • DLQ after N attempts; DLQ consumers must be manual/ops-driven.

Repositories & Specifications

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    IReadOnlyList<(Expression<Func<T, object>> KeySelector, bool Descending)> OrderBy { get; }
    int? Skip { get; }
    int? Take { get; }
}

public sealed class Specification<T> : ISpecification<T>
{
    public Expression<Func<T, bool>> Criteria { get; init; } = _ => true;
    public List<(Expression<Func<T, object>> KeySelector, bool Descending)> OrderByInternal { get; } = new();
    public int? Skip { get; init; }
    public int? Take { get; init; }
    public IReadOnlyList<(Expression<Func<T, object>> KeySelector, bool Descending)> OrderBy => OrderByInternal;
}

public interface IRepository<TAgg, TId> where TAgg : AggregateRoot<TId>
{
    Task<TAgg?> GetAsync(TId id, CancellationToken ct);
    Task SaveAsync(TAgg aggregate, CancellationToken ct); // atomic with outbox
}

public interface IReadRepository<T, TDto>
{
    Task<PagedResult<TDto>> QueryAsync(ISpecification<T> spec, CancellationToken ct);
}

public sealed record PagedResult<T>(IReadOnlyList<T> Items, int Total, int Page, int PageSize);

Query whitelisting

  • Specifications are composed from approved predicates and projections.
  • Deny arbitrary dynamic fields; map API query params → predefined ISpecification<T> builders.

Consistency Rules

Transactional boundaries

  • A single command should mutate one aggregate. Cross-aggregate work uses a saga/process manager.
  • Persist aggregate and outbox in the same transaction to ensure exactly-once publication semantics (with at-least-once delivery).

Eventual consistency guides

  • Downstream read models rebuild from events; consumers must be idempotent.
  • Use inbox dedupe on every consumer group.
  • Prefer compensations over distributed transactions.

Idempotency keys

  • Command layer accepts an optional IdempotencyKey; store on aggregate to detect replays.
  • Ingest paths (e.g., usage events) require keys and reject duplicates.

Mapping to Platform Enums (reference)

  • Access/Scope → Scope (Identity): e.g., billing.read, config.write.
  • Limit/Reset → QuotaPolicy.ResetPeriod & Window (Metering): Hour|Day|BillingCycle.
  • BillingCycle/PricingType → BillingCycle smart enum (Monthly/Quarterly/Yearly), PricingModel.Kind (Flat/PerSeat/TieredUsage/Hybrid).

Testing Templates (brief)

[TestClass]
public class SubscriptionTests
{
    [TestMethod]
    public void Activate_FromTrial_EmitsSubscriptionActivated()
    {
        var s = Subscription.Start("sub-1", "t-1", "plan-1");
        s.Activate();
        var events = s.DequeueUncommitted().ToList();

        Assert.IsTrue(events.Any(e => e.EventType == "subscription.activated"));
        Assert.AreEqual(SubscriptionStatus.Active, s.Status);
    }
}

Observability Hooks

  • Enrich domain logs with tenantId, aggregateId, eventType, version, correlationId.
  • Emit metrics: domain_events_published_total, outbox_attempts_total, inbox_duplicates_total, saga_compensations_total.

Migration & Backfills

  • Provide event replay harnesses to rebuild read models.
  • Maintain snapshot support for large aggregates (versioned snapshot every N events).

Conclusion & Summary

This blueprint distills the ConnectSoft SaaS Platform into clear, ownable domains with explicit contracts. Each bounded context speaks its own ubiquitous language, enforces its invariants through aggregates, and communicates through well-versioned events and APIs. Together, they form a factory-grade foundation that is safe to evolve, straightforward to operate, and ready for AI-assisted delivery—without sacrificing isolation or compliance.

What this blueprint gives you

  • A strategic map of capabilities and context relationships that prevents model leakage and team thrash.
  • Tactical building blocks—aggregates, entities/VOs, smart enums, repositories/specifications, events, outbox/inbox—that you can lift into code as-is.
  • Contract-first integration patterns (event-first, ACLs, webhooks) ensuring interoperability without coupling.
  • Governance by design via versioning rules, invariants, auditability, and security/compliance hooks.

Pillars that anchor the platform

  • Multi-tenancy & residency: Tenant identity and data placement are first-class, enforced at storage and message boundaries.
  • Identity & authorization: RBAC + ABAC with edition-aware entitlements in tokens; federation/SCIM without leaking external models.
  • Catalog truth: Products, editions, features, SLAs, and entitlements are authoritative and drive downstream behavior.
  • Commerce correctness: Subscriptions, pricing, rating, invoices, and dunning align with usage and entitlements, provider-agnostic behind an ACL.
  • Runtime control: Flags/settings with deterministic precedence, kill switches, and low-latency evaluation with cache coherency.
  • Usage you can trust: High-throughput intake, anti-tamper aggregation, quota signals, and billing handoff.
  • Compliance everywhere: Immutable audit streams, retention/legal hold, redaction/minimization from write to read.
  • Extensibility: Templated comms and signed webhooks with rate limits, preferences, and replay protection.
  • AI with guardrails: Agentic flows that only propose changes via PRs, with least-privilege tool grants and complete provenance.

Cross-context contract highlights (at a glance)

  • Tenant → upstream for Identity, Catalog, Billing; emits lifecycle events consumed by others.
  • Catalog → publishes entitlements.changed consumed by Identity (claims), Config (defaults), Billing (SKUs), and Metering (dimension mapping).
  • Billing ↔ Metering → invoices derive from usage; subscription changes emit entitlements.updated.
  • Config ↔ Identity → evaluation can use entitlements/roles; kill switches override downstream behavior safely.
  • Audit → append-only records from every context; queries/export under retention and legal hold.
  • Notifications/Webhooks → downstream of all; signed, idempotent, rate-limited.

Guardrails for safe evolution

  • One command → one aggregate; cross-aggregate workflows via sagas with compensations.
  • Additive-first contracts; breaking changes get new versions/topics/paths.
  • Outbox/inbox everywhere; consumers are idempotent by design.
  • Security/compliance defaults: classification and redaction, least-privilege keys, encrypted secrets, full audit with correlation.

Implementation read-off checklist

  • Establish context repos and owners; seed code with the aggregate and repository templates.
  • Stand up event topics per context with outbox publishing and inbox dedupe.
  • Materialize read models needed for UI/ops (Directory, EntitlementsView, RealTimeCounters, Ledger, AuditTimeline).
  • Wire policy engines (limits, proration, deprecation, precedence, dunning) behind application services.
  • Enable admin and evaluation APIs with tenant-scoped auth; ship SDKs for config evaluation.
  • Configure observability: structured logs with correlation, metrics for event flow and retries, traces across services.
  • Enforce ADR-driven change management; every contract change accompanies an ADR and version bump.
  • Integrate AI orchestration behind PR-only mutations with CI checks and reviewers.

Final note

This document is the stable scaffold for implementation. Teams can now open ADRs, create epics/features, and spin up services per bounded context with confidence that their models, contracts, and guardrails align. As the platform grows, iterate by extending—not breaking—the languages and contracts defined here.