Skip to main content

DDD & Bounded Contexts

:::info Source Sourced from docs/02-ddd-bounded-contexts.md in the documentation repo. :::

Companion: 01 Enterprise Architecture · 03 Microservices · 04 Event-Driven Architecture · 12 Data Models · 13 Security & Tenancy

This document is the authoritative DDD view of Ghasi-edTech. It enumerates bounded contexts, aggregates with codified invariants, ACL translators, value objects allowed to cross context boundaries, the conflict-resolution matrix, and the ubiquitous language per context. All other documents defer to this one for strategic domain definitions.

1. Strategic DDD

1.1 Domain Classification

ClassContextsRationale
Core (competitive differentiator)Authoring, Delivery, Progress (LRS), Assignment, Marketplace, AI Services, Offline SyncWhere Ghasi-edTech earns its keep
Supporting (specific, not differentiating)Catalog, Content-Packaging, Assessment, Certification, Enrollment, Search, Analytics, Compliance & auditNecessary but pattern-driven
Generic (commodity)Identity, Tenant, Billing, Notification, MediaCould be replaced by SaaS, kept in-house for isolation + cost

1.2 Context List (20 logical contexts)

  1. Identity & Access
  2. Tenant
  3. Catalog
  4. Authoring
  5. Content-Packaging
  6. Marketplace
  7. Billing
  8. Enrollment
  9. Assignment
  10. Delivery
  11. Progress (LRS)
  12. Assessment
  13. Certification
  14. Notification
  15. Media
  16. Search
  17. Analytics
  18. AI Services
  19. Offline Sync
  20. Compliance & audit

system.md originally grouped 18 contexts treating Sync as platform-infra. Sync is listed as its own context for ownership clarity; Compliance & audit is the bounded context behind compliance-service (GDPR DSR orchestration, audit coordination). Totals across other docs should align with 03 Microservices (20 logical services) unless a document explicitly scopes a subset.

2. Context Map (Relationship Patterns)

Every cross-context relationship is one of: Shared Kernel (SK), Customer/Supplier (CS), Conformist (CF), Anti-Corruption Layer (ACL), Open Host Service (OHS), Published Language (PL), or Partnership (PA). No context has "direct dependency" that isn't one of these.

Upstream → DownstreamPatternNotes
Tenant → every contextSK on TenantId value object onlySee §5 for full list of crossable VOs. No shared entities.
Identity → every contextCF for JWT claimsIdentity issues JWT; consumers conform to claim contract.
Authoring → Content-PackagingCS + ACLAuthoring emits authoring.course_draft.published.v1; Content-Packaging translates authoring's block tree into a PlayPackage (different model).
Content-Packaging → DeliveryPL (PlayPackage schema)PlayPackage is the published, versioned contract.
Content-Packaging → CatalogCSCatalog creates a CourseVersion aggregate on content.play_package.built.v1.
Marketplace → BillingCSMarketplace orchestrates payment via billing events.
Marketplace → EnrollmentCS via sagaPurchase saga → marketplace.license.granted.v1 → Enrollment creates Enrollment.
Assignment → EnrollmentCSCompliance window → Enrollment spawned on first play.
Delivery → ProgressCSDelivery emits intents; Progress is authoritative LRS.
Delivery → AssessmentCSPlayer delegates quiz serving + scoring.
Progress → CertificationCSCompletion event triggers certificate issuance.
AI Services → every content/learning contextOHS + PLGateway publishes one stable AIClient port; all AI artifacts carry a shared AIProvenance VO.
Offline Sync → every replicable contextOHS + CFSync publishes one protocol; every replicable context conforms.
Tenant → Compliance & auditCSTenant-scoped data residency and policy flags feed DSR orchestration.
Compliance & audit → NotificationCSSubject-request lifecycle notifications (export ready, erasure status, etc.).
Analytics → Compliance & auditCSAudit exports and Merkle anchoring handoff (see EP-21).
Compliance & audit → AI ServicesCSCompliance review of prompts, completions, and provenance records (admin paths).
External IdPs → IdentityACLOIDC/SAML normalized into internal VOs.
External LLMs → AI ServicesACLVendor responses normalized into AICompletion, Embedding, AIArtifact.
External Payment Gateways → BillingACLVendor tokens normalized into Payment, Invoice, Payout.
External SCORM Sources → Content-PackagingACL3rd-party zips normalized into PlayPackage projection.

2.1 Visual Context Map

┌─────────────┐
│ Tenant │ (SK: TenantId VO)
└──────┬──────┘

┌──────────────┐ │ ┌──────────────┐
│ Identity │────┴────│ Notification│
│ (CF via JWT)│ └──────────────┘
└──────┬───────┘

┌──────────┴─────────┐
│ │
┌────▼────┐ ┌─────▼─────┐ ┌──────────────┐
│ Catalog │◄────────┤ Authoring │◄────────│ Content-Pkg │
│ (CS) │ (CS) │ (Core) │ (ACL) │ (PL) │
└────┬────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
┌────▼─────────┐ ┌─────▼─────┐ │
│ Marketplace │ │ AI Svc │◄── OHS: consumed by every authoring/learner context
│ (Core saga) │ │ (OHS+PL) │
└────┬─────────┘ └─────┬─────┘
│ │
┌────▼────┐ ┌─────▼─────┐
│ Billing │ │ Assessment│
│ (ACL) │ │ (CS) │
└────┬────┘ └─────┬─────┘
│ │
┌────▼────────┐ ┌─────▼─────┐ ┌──────────────┐
│ Enrollment │◄────┤ Assignment│ │ Certification│
│ (CS) │ (CS)│ (Core) │ │ (CS) │
└────┬────────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
└─────┬──────────────┘ │
│ │
┌─────▼─────┐ ┌──────────┐ ┌──────▼──────┐
│ Delivery │───►│ Progress │───►│ Analytics │
│ (Core) │(CS)│ (LRS) │(CS)│ (CS) │
└─────┬─────┘ └──────────┘ └─────────────┘

┌─────▼─────┐ ┌──────────┐ ┌──────────────┐
│ Media │ │ Search │ │ Offline Sync │ (OHS cross-cutting)
└───────────┘ └──────────┘ └──────────────┘

3. Ubiquitous Language — Named Terms Per Context

To prevent linguistic collisions, every context defines its own spelling of shared concepts. The tables below include (a) terms unique to a context and (b) explicit disambiguation for shared names.

3.1 Identity & Access

Unique: User, Account, Session, Credential, Device, TrustedDevice, MFAFactor, APIKey, ServiceAccount, IdentityProvider, Claim, PasswordPolicy. Disambiguation: "User" in Identity refers strictly to the authentication subject — not the organizational person. Organizational membership lives in Tenant.

3.2 Tenant

Unique: Tenant (root), TenantType, OrgUnit, Role, Membership, TenantSettings, DataResidency, Plan, FeatureFlagOverride, DynamicGroup. Disambiguation: "Role" here is a tenant-scoped RBAC aggregate, not an Identity concept.

3.3 Catalog

Unique: Course (published-only form), CourseVersion, Module, LessonRef, Taxonomy, Tag, Category, Visibility, Locale. Disambiguation: Course in Catalog is immutable + published. It is not the same aggregate as CourseDraft in Authoring; they are linked by event (content.play_package.built.v1) and share only the CourseId VO.

3.4 Authoring

Unique: CourseDraft (root), ModuleDraft, LessonDraft, Block (discriminated union), MediaRef, InteractionDef, BranchingNode, DraftVersion, AIDraftBlock, CollaborationSession. Disambiguation: CourseDraft is the mutable workspace aggregate. The published catalog Course is a different aggregate in a different context.

3.5 Content-Packaging

Unique: PlayPackage (root), PackageManifest, AssetReference, SCORMExport, HTMLExport, xAPIExport, PlayPackageBundle, LicenseEnvelope, PackageHash, PackageSignature. Disambiguation: PlayPackage is the runtime projection — deliberately different shape from CourseDraft.

3.6 Marketplace

Unique: Listing (root), PricingPlan, License, Order, OrderLine, PaymentIntent, Refund, PayoutSchedule, ProviderEarnings, CouponCode, MarketplaceVisibility.

3.7 Billing

Unique: Subscription (root), Invoice, Payment, Customer, TaxJurisdiction, DunningProcess, Payout, PayoutBatch.

3.8 Enrollment

Unique: Enrollment (root), Seat, EnrollmentState, CourseVersionRef. Disambiguation: An Enrollment is per learner per course-version. Licenses (Marketplace) and Assignments (Assignment) are upstream causes, never sub-entities here.

3.9 Assignment

Unique: Assignment (root), AssignmentTarget, RecurrenceRule (RRULE), DueDate, GracePeriod, EscalationPolicy, ComplianceWindow.

3.10 Delivery

Unique: PlaySession (root), NavigationCursor, PlayState, AssistantTurn, OfflineMount.

3.11 Progress (LRS)

Unique: Statement, Attempt, InteractionResult, CompletionRecord, PassFailRecord, Activity, Verb, Actor. Disambiguation: Actor here is an xAPI actor projection — not User (Identity) nor Membership (Tenant).

3.12 Assessment

Unique: QuizBank, Question, BranchingScenario (root), ScenarioNode, DecisionEdge, AttemptResult, GradingRule.

3.13 Certification

Unique: Certificate (root), CertificateTemplate, IssuanceProof, RevocationRecord, VerificationToken, OfflineIssuanceClaim.

3.14 Notification

Unique: Notification (root), DeliveryChannel, Template, Preference, Digest, DeliveryAttempt, BounceRecord.

3.15 Media

Unique: MediaAsset (root), AssetVariant, TranscodeJob, CaptionTrack, SubtitleTrack, AIMediaArtifact.

Unique: SearchableDocument, IndexAlias, SemanticEmbedding, Recommendation, Ranker.

3.17 Analytics

Unique: Event, MetricDefinition, Dashboard, Report, ExportJob, CohortDefinition.

3.18 AI Services

Unique: Prompt (root), PromptVersion, Model, AICompletion, Embedding, AIArtifact, SafetyVerdict, AIBudget, AIAuditEntry, PromptInjectionSignal.

3.19 Offline Sync

Unique: SyncRegistration, SyncCursor, LocalMutation, RemoteDelta, VectorClock, ConflictRecord, DeviceBinding.

3.20 Compliance & audit

Unique: SubjectRequest (export/erasure), GdprDataExportRequest, ErasureRequest (planned), LegalHold (planned), BundleManifest, AuditAnchorRef (planned), ComplianceReviewSession (planned). Disambiguation: ComplianceWindow (§3.9 Assignment) is assignment scheduling policy — not this context. Compliance & audit here means regulatory data-subject rights orchestration and platform audit coordination owned by compliance-service (03 — compliance-service).

4. Aggregates — Codified Invariants

Every aggregate root below is stated with: Identity, Ownership, State Machine, Invariants (enforced in the domain layer), and Cross-Aggregate Rules (enforced by application layer or saga).

4.1 CourseDraft (Authoring)

  • Identity: CourseDraftId.
  • Ownership: TenantId, authors: UserId[].
  • State: editing → in_review → approved → publishing → published_idle (terminal-idle returns to editing on fork).
  • Invariants (domain):
    1. TenantId identical across every child (module, lesson, block). Cross-tenant reference throws DomainError.CrossTenant.
    2. Every Block belongs to exactly one lesson; ordering within a lesson is contiguous from 0.
    3. AI-generated blocks persist only with status='draft_ai' AND a non-null aiProvenance VO. Missing provenance throws DomainError.AIProvenanceRequired.
    4. Draft cannot transition → publishing unless every required: true block has status ∈ {'reviewed','published'} AND every media reference resolves.
    5. draftVersion is strictly monotonic.
    6. status='draft_ai' → cannot be required: true (AI must be reviewed before being required).
  • Cross-Aggregate Rules (saga): publish saga coordinates Content-Packaging + Catalog; compensation returns draft to approved with audit entry.

4.2 PlayPackage (Content-Packaging)

  • Identity: PlayPackageId.
  • Ownership: TenantId, courseVersionId.
  • State: building → built → revoked (no return to building).
  • Invariants:
    1. Immutable after built.
    2. Hash matches PackageSignature; signature verifies against tenant key.
    3. locale within tenant-supported locales.
  • Cross-Aggregate: only Content-Packaging writes; Delivery reads via PlayPackage GET API.

4.3 PlayPackageBundle (Content-Packaging)

  • Identity: BundleId.
  • Ownership: TenantId, playPackageId, enrollmentId, deviceId.
  • State: available → revoked.
  • Invariants:
    1. Encryption key derived via HKDF from (tenantKey, devicePubKey, bundleId).
    2. LicenseEnvelope.signature verifies against tenant key; envelope expiresAt in future at issuance.
    3. Bundle bound to single deviceId; cross-device decryption cryptographically impossible.

4.4 Listing (Marketplace)

  • Identity: ListingId.
  • Ownership: providerTenantId, courseId.
  • State: Draft → Submitted → Approved → Live → Suspended → Retired.
  • Invariants:
    1. Listing has ≥ 1 PricingPlan when Submitted.
    2. PricingPlan kind + currency + price required; negative price throws.
    3. A Course may have at most one Live Listing per provider tenant (uniqueness enforced at DB).

4.5 Order (Marketplace)

  • Identity: OrderId.
  • Ownership: buyerTenantId, buyerUserId.
  • State: created → pending_payment → paid → fulfilled → refunded | failed.
  • Invariants:
    1. lines[] non-empty; each line resolves to an active PricingPlan at placement time.
    2. totals equals sum of lines after tax and discount; money-in-micro-units only (no floats).
    3. Idempotency by clientMutationId.

4.6 License (Marketplace)

  • Identity: LicenseId.
  • Ownership: tenantId, listingId, courseId.
  • Invariants:
    1. remainingSeats ≤ seats.
    2. validUntil ≥ validFrom.
    3. State transitions active → expired | revoked; no return to active.

4.7 Assignment (Assignment)

  • Identity: AssignmentId.
  • Ownership: tenantId, createdBy.
  • State: draft → active → paused → archived (paused can return to active).
  • Invariants:
    1. rrule is a valid RFC 5545 string OR null (one-shot).
    2. dueOffset and gracePeriod are positive ISO durations.
    3. AI-suggested assignments require human approval before active.

4.8 ComplianceWindow (Assignment)

  • Identity: composite (assignmentId, userId, occurrenceStart) or surrogate ULID.
  • State: open → in_progress → completed | overdue | closed_missed.
  • Invariants:
    1. At most one open | in_progress window per (assignmentId, userId) at any time (enforced by unique partial index).
    2. graceUntil ≥ dueAt ≥ occurrenceStart.

4.9 Enrollment (Enrollment)

  • Identity: EnrollmentId.
  • Ownership: tenantId, userId, courseId, courseVersionId.
  • State: active → completed | expired | revoked (terminal).
  • Invariants:
    1. Unique on (userId, courseVersionId, source.ref).
    2. Transition completed → active forbidden.
    3. source.kind ∈ {assignment, purchase, manual, self_signup}.

4.10 PlaySession (Delivery)

  • Identity: PlaySessionId.
  • Ownership: tenantId, userId, enrollmentId, deviceId.
  • State: init → active → paused → completed | abandoned.
  • Invariants:
    1. At most one active session per (userId, courseVersionId, deviceId) at a time.
    2. Cursor references blocks that exist in the referenced PlayPackage.
    3. Offline-mounted sessions require valid OfflineMount (signature + hash verified).

4.11 Attempt (Progress / LRS)

  • Identity: AttemptId.
  • Ownership: tenantId, userId, enrollmentId, courseVersionId.
  • State: open → closed.
  • Invariants:
    1. Append-only Statements; open → open permitted; closed → open forbidden.
    2. Statements idempotent on (statementId) and content hash.
    3. Terminal verb (completed | failed | abandoned) triggers close.

4.12 Certificate (Certification)

  • Identity: CertificateId.
  • Ownership: tenantId, userId, courseVersionId, enrollmentId.
  • State: pending_offline_verification → issued | revoked (terminal).
  • Invariants:
    1. proof (JWS) verifies against tenant signing key.
    2. evidence.completionRecordId references a valid CompletionRecord.
    3. Provisional certificates must carry OfflineIssuanceClaim.signedByDevice.

4.13 Prompt (AI Services)

  • Identity: PromptId.
  • Ownership: tenantId | null (null = system prompt).
  • State per version: draft → active → deprecated.
  • Invariants:
    1. A Prompt has ≥ 1 version; only one active version at a time per tenant.
    2. Version bump requires passing eval suite before active.
    3. Template variables ⊆ inputSchema properties.

4.14 AICompletion (AI Services)

  • Identity: CompletionId.
  • Ownership: tenantId, userId, promptId?, modelId.
  • Invariants:
    1. Immutable once written.
    2. Must carry SafetyVerdict for both input and output.
    3. decisionId written when HITL acceptance occurred downstream.

4.15 LocalMutation (Offline Sync — client + server-replica)

  • Identity: clientMutationId.
  • Invariants:
    1. Idempotent by clientMutationId — repeat submission is a no-op.
    2. vectorClock[deviceId] is strictly monotonic per entity per device.
    3. baseVersion (if present) must match some real server version or be null (create).
    4. State transitions: queued → inflight → applied | conflicted | rejected (terminal).

4.16 SyncCursor (Offline Sync)

  • Identity: (tenantId, userId, deviceId, scope).
  • Invariants:
    1. lamport is strictly monotonic per (tenant, scope).
    2. Client advances cursor only after server-confirmed pull.

5. Value Objects Allowed to Cross Context Boundaries

Only the following value objects may appear across bounded-context lines. Anything else must be translated through an ACL.

Value ObjectShapeWhere used
TenantIdbranded stringEvery aggregate everywhere
UserIdbranded stringEvery context referencing a user
DeviceIdbranded stringIdentity → Content-Packaging, Delivery, Sync
CourseIdbranded stringAuthoring ↔ Catalog ↔ Marketplace ↔ Enrollment
CourseVersionIdbranded stringCatalog ↔ Content-Packaging ↔ Delivery ↔ Enrollment ↔ Progress ↔ Certification
PlayPackageIdbranded stringContent-Packaging ↔ Delivery
BundleIdbranded stringContent-Packaging ↔ Delivery ↔ Sync
EnrollmentIdbranded stringEnrollment ↔ Delivery ↔ Progress ↔ Certification ↔ Marketplace
ListingIdbranded stringMarketplace ↔ Catalog (read-only)
OrgUnitIdbranded stringTenant ↔ Assignment ↔ Analytics
AssignmentIdbranded stringAssignment ↔ Enrollment
MediaAssetIdbranded stringMedia ↔ Authoring ↔ Content-Packaging
Locale (BCP 47)stringEverywhere
ISODatestringEverywhere
Money { amountMicro, currency }recordMarketplace ↔ Billing ↔ Analytics
AIProvenancerecord (see §6)AI Services → every context that persists AI artifacts
EventEnvelope headerssee 04 EDAEvery NATS event
LicenseEnveloperecord (see 03 content-service)Content-Packaging ↔ Delivery ↔ Sync
VectorClock { [deviceId]: n }recordSync ↔ every replicable context

Entities (aggregate roots or members) are never shared across contexts. If another context needs data about an entity, it consumes events and projects a read model in its own schema.

6. AIProvenance — Mandatory Shared Value Object

Every aggregate that can hold AI-generated content must define a aiProvenance?: AIProvenance field. The domain layer refuses persistence of any AI-marked artifact without one.

interface AIProvenance {
model: string; // e.g., 'chat-large-v4'
version?: string;
promptId?: string;
promptVersion?: SemVer; // pinned at call time
traceId: string; // W3C traceparent
decisionId?: string; // ties to HITL acceptance record
local: boolean; // true if produced by local-inference
generatedAt: ISODate;
reviewedBy?: UserId;
reviewedAt?: ISODate;
cost?: { microUSD: number; tokens: { in: number; out: number } };
safety: { input: SafetyVerdict; output: SafetyVerdict };
cacheHit: boolean;
}

Downstream consumers may filter, display, or audit any artifact using only this VO — there is no need to join into AI Services tables.

7. Conflict-Resolution Matrix (Offline Sync)

Every replicable aggregate declares exactly one conflict policy. The matrix below is canonical; any new aggregate must be added here.

Aggregate / EntityPolicyRationaleResolver
Statement (xAPI)append_onlyStatements are immutable facts with their own timestampserver merges; duplicates de-duped by (actor, verb, object, timestamp, statementId)
Attemptappend_only for child Statements; server_authoritative for stateClose is server-driven by terminal verbserver
AssistantTurn (Delivery)append_onlyImmutable conversation logserver
Block (Authoring, live collab online)crdt_yjsLive collaboration uses YjsYjs merge in sync-service
Block (Authoring, offline edit)lww + diff UI on non-CRDT fields; CRDT merge for textCRDT can handle co-existing offline edits to same text; non-text fields surface conflictuser picks in conflict UI
CourseDraft metadatalwwTitle/description rarely conflictserver LWW by timestamp
PlaySession cursormax-ofFurthest progress winsserver computes max cursor by module/lesson/block index
Enrollmentserver_authoritativeEnrollment is a server-issued factclient mutation rejected with current server state
Assignmentserver_authoritativeAdmin-only aggregaterejected
ComplianceWindowserver_authoritativeServer materializes RRULErejected
Licenseserver_authoritativeLicense grant is server-issuedrejected
Orderserver_authoritativePayment saga is server-drivenrejected
MediaAsset (metadata)lww for ad-hoc edits; server_authoritative for statusready/quarantined is server-decidedserver
Notification preferenceslww by updatedAtUser may edit from multiple deviceslast wins
Certificateserver_authoritative post-verificationProvisional claims allowed (append-only), final cert server-issuedserver
OfflineIssuanceClaimappend_onlyImmutable claimserver verifies + decides
SearchableDocumentprojectionRead-model only; rebuilt from eventsN/A
Taxonomyserver_authoritativeAdmin-onlyrejected
FeatureFlagOverrideserver_authoritativeAdmin-onlyrejected

All conflict records are retained 90 days in sync.conflicts for audit regardless of resolution.

8. Anti-Corruption Layers (ACLs)

Each ACL owner below holds the responsibility to keep external chaos out of the domain.

ACL OwnerExternal InputNormalized Internal Shape
identity-serviceOIDC IdToken, SAML Assertion, WebAuthn credential; upstream brokers (Keycloak, Firebase, Okta, Cognito) via OIDC discoveryUser, Session, IdentityClaim, Credential
ai-gateway-serviceVendor LLM/embedding/TTS/image responsesAICompletion, Embedding, AIArtifact, SafetyVerdict
billing-servicePayment processor webhook payload, tax vendor responsePayment, Invoice, Payout, TaxLine
content-service3rd-party SCORM 1.2 zipPlayPackage, PackageManifest, AssetReference
notification-serviceEmail/SMS/push provider webhook (bounce/complaint)DeliveryAttempt, BounceRecord, SuppressionEntry
media-serviceUploaded binary + MIME, external capture streamsMediaAsset, AssetVariant, CaptionTrack

ACL contract: the internal shape is strictly typed; no raw vendor types ever escape the ACL adapter into the application or domain layers. ACL adapters live in infrastructure/adapters/ per service, never in domain/ or application/.

9. Integration Mechanism — Events Only

The only approved cross-context integration mechanism is a domain or integration event on NATS JetStream (subject pattern {service}.{aggregate}.{event}.vN). All other mechanisms are explicitly forbidden:

  • ❌ Direct cross-service DB reads.
  • ❌ Shared database schemas.
  • ❌ Synchronous chains > 2 hops (enforced at API gateway + observability).
  • ❌ Cross-context ORM joins.

Exceptions (narrowly allowed): low-latency request/reply via NATS for tenant.resolve_for_request (identity-related context resolution at request-entry) and ai-gateway-service for per-call routing. Both are capped at 1 hop and heavily cached.

10. Aggregates Boundary Rule (What Lives Where)

ConcernLives inDoes NOT live in
Credentials + authenticationIdentityTenant, application services
User profile, role, org membershipTenantIdentity
Course definition (mutable)AuthoringCatalog
Course definition (published, immutable)Catalog (metadata) + Content-Packaging (artifact)Authoring
Quiz scoring rulesAssessmentAuthoring
Quiz scoring executionAssessmentDelivery
Statement persistenceProgressDelivery
Recurrence rule evaluationAssignmentNotification
Offline bundle signingContent-PackagingDelivery
Offline bundle decryptionPlayer runtime (client)Server
Offline mutation queueSync (client + server)Each domain reinventing it
AI prompt registryAI ServicesConsumer services
AI safety enforcementAI ServicesEach consumer
Tenant isolation enforcementEvery layer(cross-cutting, not localized)

11. Clean Architecture Enforcement Per Context

Every service enforces the same layered discipline. The rules below are non-negotiable and verifiable by dependency-graph analysis in CI.

11.1 Domain Layer (/domain)

  • Pure TypeScript. No import from NestJS, TypeORM, Prisma, NATS client, Next.js, axios, fetch, Redis, pgvector, AWS SDK, Zod.
  • Allowed: crypto (browser + node), date-fns-pure (no i/o), pure data transformations.
  • Forbidden: any I/O, any framework decorator, any dynamic import, any side-effectful constructor.
  • Tested with: Vitest, no mocks beyond the domain itself.

11.2 Application Layer (/application)

  • Owns: use-cases, command handlers, DTO assembly, mapping, orchestration of ports.
  • Depends on: domain only (compile-time).
  • Exposes ports/ interfaces: AIClient, EventPublisher, EmailSender, IdentityResolver, SyncClient, Clock, Random, IdGenerator, etc.
  • Forbidden: importing infrastructure, using process.env, calling fetch directly.

11.3 Infrastructure Layer (/infrastructure)

  • Owns: all adapters (Postgres repositories, NATS pub/sub, HTTP clients, S3 clients), DI wiring, environment config.
  • Depends on: application (for ports) and domain (for types only).
  • Rule: each adapter must have a corresponding port in application; anonymous infra is not allowed.

11.4 Presentation Layer (/presentation)

  • Owns: controllers, GraphQL resolvers (optional), WebSocket handlers, SSE handlers.
  • Depends on: application.
  • Forbidden: importing domain entities directly in responses — always via DTOs.

11.5 Frontend Mirror

Next.js apps follow the same rule set with app/ + components/ (presentation) → hooks/ + services/ (application) → lib/domain/ (pure TS) → lib/adapters/ (infra). lib/domain/ has zero React imports.

12. Multi-Tenant Discipline Per Aggregate

Every aggregate root has tenantId: TenantId. The domain-layer invariants for tenancy:

  1. Every cross-reference between aggregates must share the same TenantId (checked in constructors).
  2. Every repository method requires tenantId as a parameter (not implicit).
  3. Every event payload includes tenantId (envelope + payload redundancy for defense).
  4. Every outbox write validates tenantId consistency before commit.

Postgres RLS + the DB-layer checks in 13 Security & Tenancy §4 are the second line of defense.

13. Why This Design (DDD + Clean Architecture + AI/Offline)

  • DDD: Bounded contexts aligned to team ownership + ubiquitous language. Aggregates protect invariants at the domain layer; cross-context relationships are explicit (SK, CS, CF, ACL, OHS, PL). No ambiguous couplings.
  • Clean Architecture: Domain has zero framework ties. Infrastructure is replaceable — Postgres → another RDBMS, NATS → another broker — without touching use-cases.
  • Event-driven: Enables decoupling, replay, audit, and offline sync over the same event log.
  • AI-first: A single AIProvenance VO and a single AIClient port make AI governance consistent across 19 services. Provenance is a domain-layer invariant, not a logging concern.
  • Offline-first: A single LocalStore port, a single sync protocol, and a canonical conflict-resolution matrix mean every context behaves predictably when the network drops.

If this document ever disagrees with another document in the set, this document is authoritative for strategic DDD; the other document must be updated.