Skip to content

Latest commit

 

History

History
1000 lines (814 loc) · 42.7 KB

File metadata and controls

1000 lines (814 loc) · 42.7 KB

Evolith Tracker — Technical Architecture Design

Bilingual Navigation: English (this document) · Versión en Español

Document Status: Draft
Type: Technical Architecture Design (TAD)
Satellite: Evolith Tracker
Upstream: Evolith Core
Reference Implementation: UMS (technical patterns only)
Date: 2026-06-07
Author: Architect Agent (BMAD)


Part I — Context and Architecture Vision

1. System Context

C4Context
    title System Context — Evolith Tracker (Governance Control Plane)

    Person(human, "Human Actor", "Governs, authorizes exceptions, and approves Gate Decisions")
    Person(agent, "Autonomous Agents", "Executes bounded tasks and provides TechnicalEvaluationResults via MCP")

    System(evolith_tracker, "Evolith Tracker", "E2E AI-Native SDLC Orchestrator. Owns canonical Gate Decisions and Evidence Graph.")

    System_Ext(evolith_core, "Evolith Core", "Upstream Constitution (Rulesets, Schemas, Contracts)")
    System_Ext(ums, "UMS SaaS", "AuthN/AuthZ, authorization graph")
    System_Ext(providers, "External Providers", "SCM, CI/CD, Work Systems, Observability")

    Rel(human, evolith_tracker, "Approves transitions")
    Rel(agent, evolith_tracker, "Evaluates criteria (stateless)")

    Rel(evolith_tracker, evolith_core, "Resolves policies")
    Rel(evolith_tracker, ums, "Validates identity")
    Rel(evolith_tracker, providers, "Ingests evidence via ACLs")
Loading

2. Architecture Positioning

Evolith Tracker is a Progressive Monolith — a single backend deployment unit with strict internal modularity. The 5 Phase Gate contexts (Discovery, Design, Construction, QA, Release) plus 4 supporting contexts (Governance, Artifacts, Metrics, Integration) are logical bounded contexts that can be extracted into independent services in future phases without rewriting domain logic. The frontend is a Microfrontend architecture (Module Federation) per T-002.

React Microfrontends (Shell Host + 7 remotes, PWA)   ← §20
         ↓  (REST only — OpenAPI 3.0; see T-009)
  API Gateway / BFF
         ↓
  NestJS Monolith (9 bounded contexts)
    ├── Discovery Module        (Phase Gate 1)
    ├── Design Module           (Phase Gate 2)
    ├── Construction Module     (Phase Gate 3)
    ├── QA Module               (Phase Gate 4)
    ├── Release Module          (Phase Gate 5)
    ├── Governance Module       (Gate Decision Engine, Process Orchestrator)
    ├── Artifacts Module        (Evidence Graph, Artifact schemas)
    ├── Metrics Module          (Scorecards, DORA/SPACE)
    ├── Integration Module      (Provider Ports & ACLs)
    └── Shared Infrastructure   (Unit of Work, Event Bus + Outbox, Tenant Context)
         ↓
  PostgreSQL (schema-per-context — tracker_*, see T-008)
         ↓
  Evolith Core SDK/CLI/MCP
  UMS Auth API
  External ACL Adapters

### 2.1 Gate Evaluation and Decision Separation

Following the **Governed Composition** vision, Tracker enforces a strict separation of concerns regarding who evaluates and who decides:

- **TechnicalEvaluationResult:** Produced by stateless evaluators (CLI, MCP, Agents) against versioned rules. It is merely an assessment of evidence (`compliant`, `non_compliant`, `error`).
- **GateDecision:** The canonical governance decision owned exclusively by Tracker (`approved`, `rejected`, `approved_with_exception`). A green pipeline or successful agent response cannot independently advance a phase; Tracker must combine the evaluations, tenant policies, and human approvals to produce the final `GateDecision`.

Part II — Bounded Context Map

3. Bounded Contexts and Modules

Bounded Context Source Module Name Phase Gate Schema PostgreSQL Aggregate Roots
Discovery PRD EPIC-000, Discovery DDD @evolith/tracker-discovery Gate 1 tracker_discovery Initiative, Backlog, Epic, UserStory
Design PRD EPIC-001, Design DDD @evolith/tracker-design Gate 2 tracker_design TechnicalBlueprint, TechnicalContract, ADR, DataSchema
Construction PRD EPIC-002, Construction DDD @evolith/tracker-construction Gate 3 tracker_construction ImplementationCycle, TechnicalStory, PeerReview
QA PRD EPIC-003, QA DDD @evolith/tracker-qa Gate 4 tracker_qa TestCycle, TestExecution, Defect
Release PRD EPIC-004, Release DDD @evolith/tracker-release Gate 5 tracker_release ReleasePackage, DeploymentRecord, Environment
Governance PRD EPIC-005, EPIC-006 @evolith/tracker-governance All tracker_governance SatelliteProduct, SDLCExecution, PhaseGateState, ExceptionRequest
Artifacts PRD Artifacts @evolith/tracker-artifacts All tracker_artifacts ArtifactDefinition, ArtifactInstance, EvidenceRecord
Metrics PRD EPIC-006, Scorecards @evolith/tracker-metrics All tracker_metrics ScorecardDefinition, MetricSnapshot, DriftAlert
Integration PRD ACLs @evolith/tracker-integration All tracker_integration IntegrationEndpoint, SyncRecord, ACLAdapter
Geo (Master Data) T-032 @evolith/tracker-geo All (supporting) geo (deliberate deviation from T-008 — separable generic subdomain) Country, Location, Currency, ExchangeRate, TaxScheme, LocaleProfile

Geo / Master-Data is a supporting/generic subdomain (reference data: regionalization, localization, ubigeo, currencies, taxes) — not an SDLC phase context. It replaces the free-field TenantLocalization with validated master catalogs, and is accessed only through the IMasterDataDirectory port + a soft-reference snapshot (geoId), never a cross-context JOIN. It owns its schema geo so it can be extracted to a shared evolith_mms service (satellite + platform service, like UMS) in one step when a second SaaS consumer appears. See T-032 and the Geo diagram.


Part III — Domain Model

4. Discovery Bounded Context

Ubiquitous Language

  • Initiative: Business container that consolidates an idea before splitting into epics
  • Discovery Canvas: Structured registration of problem, ROI, KPIs, risks
  • Approval Gate: Go/No-Go validation checkpoint
  • Backlog: Prioritized inventory of User Stories from approved Initiative
  • Requirement Checklist: Dynamic workflow steps (mandatory/optional) per tenant

Aggregate Roots

Aggregate Identity Responsibilities Child Entities
Initiative initiativeId: UUID Canvas lifecycle, approval workflow, backlog generation, state transitions BusinessCase, TechnicalJustification, RequirementChecklist, ApprovalGate, StateTransition[], ExternalReference[]
Backlog backlogId: UUID Story ordering, reprioritization orderedStoryIds: List<UUID> (reference only)
Epic epicId: UUID Grouping of User Stories, external sync ExternalReference[]
UserStory storyId: UUID Atomic functional unit with acceptance criteria ExternalReference[]

Domain Events

  • InitiativeCreatedEvent
  • InitiativeSubmittedForApprovalEvent
  • InitiativeApprovedEvent → triggers Design phase
  • InitiativeRejectedEvent
  • InitiativePhaseAdvancedEvent

Key Commands/Queries

Operation Type Description
SubmitInitiativeCommand Command Create initiative with canvas
ApproveInitiativeCommand Command PO/Architect approves; triggers backlog
RejectInitiativeCommand Command Reject with reason
GenerateBacklogCommand Command Auto-generate stories from approved initiative
GetInitiativeStatusQuery Query Current gate, blocking conditions
ListInitiativesQuery Query Pipeline view with gate status

5. Design Bounded Context

Ubiquitous Language

  • Technical Blueprint: Master package of approved design artifacts
  • Technical Contract: OpenAPI/AsyncAPI specification generated from User Stories
  • ADR: Formal architectural decision record
  • Architecture Gate: Review checkpoint to prevent drift

Aggregate Roots

Aggregate Identity Responsibilities
TechnicalBlueprint blueprintId: UUID Lifecycle of design phase; references contracts, ADRs, schemas
TechnicalContract contractId: UUID OpenAPI/AsyncAPI content, format, deprecation
ADR adrId: UUID Title, context, decision, consequences, tags
DataSchema schemaId: UUID Database schema definition

Domain Events

  • BlueprintCreatedEvent
  • ContractSubmittedEvent
  • ContractApprovedEvent
  • DesignApprovedEvent → triggers Construction phase

6. Construction Bounded Context

Ubiquitous Language

  • Implementation Cycle: Time-boxed development effort (Sprint/Milestone)
  • Technical Story: Atomic execution unit from approved User Story
  • Code Branch: Repository and branch reference
  • Peer Review: Formal code review checkpoint via webhooks

Aggregate Roots

Aggregate Identity Responsibilities
ImplementationCycle cycleId: UUID Sprint orchestration; tracks story IDs, metrics, gate
TechnicalStory storyId: UUID Task execution, time logging, branch linking, review
PeerReview reviewId: UUID PR status, CI pipeline, reviewer decisions

Domain Events

  • CycleStartedEvent
  • StoryAssignedEvent
  • CodeCommittedEvent (triggers drift check)
  • DriftDetectedEvent
  • ReviewApprovedEvent
  • ConstructionGatePassedEvent → triggers QA phase

7. QA Bounded Context

Ubiquitous Language

  • Test Cycle: Container for test executions tied to Implementation Cycle
  • Test Execution: Traceable run of a test case
  • Defect: Flaw identified during execution; independent lifecycle for external sync
  • Quality Gate: Formal criteria for release authorization

Aggregate Roots

Aggregate Identity Responsibilities
TestCycle cycleId: UUID Test orchestration; pass rate calculation; gate evaluation
TestExecution executionId: UUID Result logging, execution status
Defect defectId: UUID Severity, priority, external sync via ACL

Domain Events

  • TestCycleStartedEvent
  • TestExecutionCompletedEvent
  • DefectRaisedEvent
  • QualityGatePassedEvent → triggers Release phase
  • QualityGateFailedEvent

8. Release Bounded Context

Ubiquitous Language

  • Release Package: Group of validated stories ready for Production
  • Environment: Logical/physical target (Staging, UAT, Production)
  • Deployment Record: Immutable deployment trace
  • Re-Do Flow: Contingency engine triggered by blockers

Aggregate Roots

Aggregate Identity Responsibilities
ReleasePackage packageId: UUID Version, release notes, story list, gate authorization
DeploymentRecord deploymentId: UUID Environment target, status, rollback URL
Environment envId: UUID Name, type (Staging/Production), configuration

Domain Events

  • ReleasePlannedEvent
  • DeploymentTriggeredEvent
  • DeploymentCompletedEvent
  • RollbackTriggeredEvent
  • ReDoFlowActivatedEvent

Part IV — Technical Architecture

9. Layer Structure (per Module)

Following ADR-0002 (Clean Hexagonal Architecture), each module follows:

modules/{context}/
├── domain/
│   ├── aggregates/
│   ├── entities/
│   ├── value-objects/
│   ├── events/
│   ├── services/
│   ├── policies/
│   ├── errors/
│   └── ports/           # Repository & service interfaces
├── application/
│   ├── commands/
│   ├── queries/
│   ├── handlers/
│   ├── services/
│   ├── dtos/
│   └── validators/
├── infrastructure/
│   ├── persistence/
│   ├── messaging/
│   ├── external/        # UMS adapter, Core SDK adapter, ACLs
│   └── observability/
└── presentation/
    ├── controllers/
    ├── dto/
    ├── guards/
    ├── pipes/
    └── interceptors/

Domain Layer Rule: Zero imports from NestJS, ORM, PostgreSQL, or external SDKs.


10. Backend Stack

Component Technology Justification
Runtime Node.js 20 LTS LTS stability, ESM support
Language TypeScript 5.x (strict) ADR-0003 compliance
Framework NestJS 10+ Modular architecture, DI, OpenAPI
ORM TypeORM ADR-0043 decision — Data Mapper pattern
Database PostgreSQL 15+ Schema-per-domain isolation
API REST + OpenAPI 3.0 Contract-first, code generation
Auth JWT + UMS Authorization Graph UMS as external IdP
Messaging In-memory Event Bus (abstraction) Ready for future broker
Validation class-validator + zod Layer-appropriate validation

11. Domain Primitives (Shared)

// Shared domain primitives — no external dependencies
interface Entity<T> {
  readonly id: T;
  equals(other: Entity<T>): boolean;
}

interface AggregateRoot<T> extends Entity<T> {
  readonly domainEvents: DomainEvent[];
  clearEvents(): void;
}

interface ValueObject<T> {
  equals(other: T): boolean;
}

interface DomainEvent {
  readonly eventId: string;
  readonly occurredAt: Date;
  readonly aggregateId: string;
  readonly eventType: string;
}

interface Result<T, TError = Error> {
  isSuccess: boolean;
  isFailure: boolean;
  value?: T;
  error?: TError;
}

12. CQRS Implementation

// Command pattern
interface Command {
  readonly commandId: string;
  readonly occurredAt: Date;
}

interface CommandHandler<TCommand extends Command, TResult> {
  handle(command: TCommand): Promise<Result<TResult>>;
}

// Query pattern
interface Query<TResponse> {
  readonly queryId: string;
}

interface QueryHandler<TQuery extends Query<TResponse>, TResponse> {
  handle(query: TQuery): Promise<Result<TResponse>>;
}

// Command Bus (infrastructure) — wraps every command in a Unit of Work
@Injectable()
export class CommandBus {
  constructor(
    private readonly handlerRegistry: HandlerRegistry,
    private readonly uow: IUnitOfWork,
    private readonly outbox: TransactionalOutbox,
    private readonly eventBus: DomainEventBus,
  ) {}

  async execute<TCommand extends Command>(command: TCommand): Promise<Result<unknown>> {
    const handler = this.handlerRegistry.get(command.constructor);

    // Unit of Work: the handler's writes AND the outbox inserts commit atomically
    // in ONE database transaction. Either everything persists, or nothing does.
    const result = await this.uow.runInTransaction(async (tx) => {
      const handlerResult = await handler.handle(command, tx);
      if (handlerResult.isSuccess) {
        // Domain events are persisted to the outbox INSIDE the same transaction.
        for (const event of handlerResult.value.domainEvents) {
          await this.outbox.addEvent(event, tx);
        }
      }
      return handlerResult; // throwing/returning failure rolls back the whole UoW
    });

    // Best-effort, low-latency in-process dispatch AFTER commit. This is an
    // OPTIMIZATION, not a guarantee — delivery is guaranteed by the OutboxProcessor
    // (see §13 caveat). A failure here is logged and ignored; the outbox will retry.
    if (result.isSuccess) {
      void this.eventBus
        .publish(result.value.domainEvents)
        .catch((err) => this.logger.warn({ msg: 'in-process dispatch failed; outbox will deliver', err }));
    }
    return result;
  }
}

12.1 Unit of Work (Transactional Boundary)

The Unit of Work is the explicit transactional boundary for a single command. It guarantees that all aggregate mutations produced by a command handler — and the domain events emitted by those mutations — are committed atomically.

// Unit of Work port (domain/application-facing abstraction — no ORM leakage)
export interface IUnitOfWork {
  // Runs `work` inside a single DB transaction. Commits on success;
  // rolls back if `work` throws or the returned Result is a failure.
  runInTransaction<T>(
    work: (tx: TransactionContext) => Promise<Result<T>>,
  ): Promise<Result<T>>;
}

// TransactionContext is the handle passed to repositories so every write in a
// command joins the SAME transaction. Backed by TypeORM's EntityManager.
export type TransactionContext = EntityManager;

// Infrastructure adapter (TypeORM — ADR-0043)
@Injectable()
export class TypeOrmUnitOfWork implements IUnitOfWork {
  constructor(private readonly dataSource: DataSource) {}

  async runInTransaction<T>(
    work: (tx: EntityManager) => Promise<Result<T>>,
  ): Promise<Result<T>> {
    return this.dataSource.transaction(async (tx) => {
      const result = await work(tx);
      if (result.isFailure) {
        // Force rollback without surfacing an exception to the caller.
        throw new RollbackSignal(result);
      }
      return result;
    }).catch((err) => {
      if (err instanceof RollbackSignal) return err.result as Result<T>;
      throw err; // genuine infrastructure error — propagate
    });
  }
}

Unit of Work rules:

Rule Rationale
One command = one transaction = one Unit of Work A command is the atomic consistency unit; partial commits are never allowed
Repositories receive the TransactionContext (tx) from the handler All writes of a command share the same transaction; no autonomous repository transactions
A command mutates one aggregate root by default Honors the Small Aggregates pattern; multi-aggregate writes require explicit justification
Outbox inserts happen inside the UoW Domain events and state mutations are committed atomically (no lost events, no phantom events)
Cross-context effects are never in the same transaction Other bounded contexts react asynchronously via the outbox — see §13
The domain layer never sees EntityManager TransactionContext is injected at the application/infrastructure boundary only

13. Service Bus and Messaging

// Domain Event Bus (in-memory, abstraction for future broker)
interface DomainEventBus {
  publish(events: DomainEvent[]): Promise<void>;
  subscribe<TEvent extends DomainEvent>(
    eventType: string,
    handler: (event: TEvent) => Promise<void>
  ): Subscription;
}

// Transactional Outbox (for cross-context consistency)
@Injectable()
export class TransactionalOutbox {
  async addEvent(event: DomainEvent, tx: EntityManager): Promise<void> {
    await tx.save(OutboxMessage, {
      id: event.eventId,
      eventType: event.eventType,
      aggregateId: event.aggregateId,
      payload: event.payload as object,
      createdAt: event.occurredAt,
      processedAt: null,
    });
  }
}

// Outbox Processor (background job) — uses TypeORM (ADR-0043)
@Injectable()
export class OutboxProcessor {
  constructor(
    @InjectRepository(OutboxMessage)
    private readonly outboxRepo: Repository<OutboxMessage>,
    private readonly eventBus: DomainEventBus,
  ) {}

  async processPending(maxMessages = 100): Promise<void> {
    const messages = await this.outboxRepo.find({
      where: { processedAt: IsNull() },
      take: maxMessages,
      order: { createdAt: 'ASC' },
    });
    for (const msg of messages) {
      await this.eventBus.publish(deserialize(msg));
      await this.outboxRepo.update(msg.id, { processedAt: new Date() });
    }
  }
}

13.1 Delivery Guarantees — In-Memory Bus Caveat

WARNING: Critical caveat: The in-memory DomainEventBus does NOT guarantee delivery across process restarts or crashes. If the process dies after a command's transaction commits but before its in-process dispatch completes, those in-memory handlers do not run. The in-memory bus is a latency optimization, never the source of truth for cross-context propagation.

The delivery guarantee comes from the Transactional Outbox, not the in-memory bus. The two work together:

Mechanism Guarantee Use
In-memory dispatch (CommandBus, post-commit) None (best-effort) Low-latency same-process reactions when the process stays alive
Transactional Outbox + OutboxProcessor At-least-once The durable, restart-safe propagation path for every domain event

Resulting contract for event consumers:

Concern Rule
Idempotency Every event handler MUST be idempotent — the outbox is at-least-once, so an event may be delivered more than once (e.g. if the process dies between eventBus.publish and the processedAt update). Consumers dedupe on eventId.
Ordering The OutboxProcessor reads ORDER BY createdAt ASC. Strict global ordering is not guaranteed across aggregates; handlers must not assume cross-aggregate order.
Recovery On restart, the OutboxProcessor picks up every row where processedAt IS NULL — no event is lost, even if the in-memory dispatch never ran.
Cross-context events Always travel via the outbox. A context never reacts to another context's change through the in-memory bus alone.
Phase 1 → future broker When a real broker (Kafka/RabbitMQ) is introduced, only the OutboxProcessor's publish target changes. The outbox-write-inside-the-UoW contract is unchanged, so domain/application code is untouched.

This makes the Phase 1 messaging design forward-compatible: the DomainEventBus interface and the outbox contract already model at-least-once semantics, so swapping the in-memory implementation for a distributed broker is an infrastructure-only change (consistent with the Progressive Monolith → distributed evolution in the Scale-Out Strategy).


Part V — PostgreSQL Schema Design

14. Schema Isolation

Each bounded context owns its PostgreSQL schema:

CREATE SCHEMA tracker_discovery;
CREATE SCHEMA tracker_design;
CREATE SCHEMA tracker_construction;
CREATE SCHEMA tracker_qa;
CREATE SCHEMA tracker_release;
CREATE SCHEMA tracker_governance;
CREATE SCHEMA tracker_artifacts;
CREATE SCHEMA tracker_metrics;
CREATE SCHEMA tracker_integration;
CREATE SCHEMA tracker_audit;  -- Append-only audit trail

15. Schema Design Principles

  1. No foreign keys between schemas — cross-context references use UUIDs only
  2. Every schema has created_at, created_by, updated_at, updated_by — audit tracking
  3. Soft deletes only when business rule requiresdeleted_at nullable
  4. UTC timestamps onlyTIMESTAMPTZ for all date columns
  5. UUID primary keysgen_random_uuid()
  6. Optimistic concurrencyxmin system column for conflict detection
  7. JSONB for flexible data — only where justified (external references, metadata)

16. Multi-Tenancy

-- Row-Level Security on tenant-scoped tables
ALTER TABLE tracker_discovery.initiatives ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON tracker_discovery.initiatives
  USING (tenant_id = current_setting('app.current_tenant_id')::UUID);

-- Set tenant context per request via PostgreSQL setting
SET app.current_tenant_id = 'tenant-uuid-here';

Part VI — Integration Architecture

17. Evolith Core Integration

// Core synchronization service
interface CoreIntegrationService {
  getCoreVersion(): Promise<CoreVersion>;
  getRulesets(context: SDLCPhase): Promise<Ruleset[]>;
  getArtifactDefinitions(phase: SDLCPhase): Promise<ArtifactDefinition[]>;
  getTaxonomy(): Promise<Taxonomy>;
  validateCompliance(artifact: Artifact, ruleset: Ruleset): Promise<ComplianceResult>;
}

// Caching strategy
interface CoreCacheService {
  getRulesets(phase: SDLCPhase): Promise<Ruleset[] | null>;
  setRulesets(phase: SDLCPhase, rulesets: Ruleset[], ttl: Duration): Promise<void>;
  invalidate(): Promise<void>;
}

Key Principle: Tracker does not copy or store Evolith Core rulesets as a second source of truth. It queries the Core at runtime (with strategic caching) and validates artifacts against live rules.

18. UMS Authentication and Authorization Integration

// UMS Security ACL — transforms UMS graph to Tracker canonical model
interface IUmsSecurityAdapter {
  validateToken(token: string): Promise<UmsTokenValidation>;
  getAuthorizationGraph(userId: string): Promise<AuthorizationGraph>;
  getEffectivePermissions(
    userId: string,
    scope: TrackerScope
  ): Promise<TrackerPermission[]>;
}

// UMS → Tracker permission mapping
const PERMISSION_MAPPING: Record<string, TrackerPermission> = {
  'ums:initiative:read': 'tracker:initiative:read',
  'ums:initiative:approve': 'tracker:initiative:approve',
  'ums:design:contract:submit': 'tracker:contract:submit',
  'ums:construction:task:complete': 'tracker:task:complete',
  'ums:qa:gate:evaluate': 'tracker:gate:evaluate',
  'ums:release:authorize': 'tracker:release:authorize',
  // ... full mapping per capability
};

// Backend authorization guard
@Injectable()
export class TrackerPermissionGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const required = this.reflector.get<TrackerPermission>(
      'requiredPermission', context.getHandler()
    );
    if (!required) return true;

    const request = context.switchToHttp().getRequest();
    const permissions = await this.umsAdapter.getEffectivePermissions(
      request.user.id,
      { tenantId: request.tenantId, initiativeId: request.initiativeId }
    );

    return permissions.includes(required) || this.isFailClosed === false;
  }
}

19. External System ACLs

// Anti-Corruption Layer for external integrations
interface ExternalSystemACL<TExternal, TCanonical> {
  ingest(externalData: TExternal): Promise<TCanonical>;
  validate(externalData: TExternal): Promise<ValidationResult>;
  normalize(externalData: TExternal): TCanonical;
}

// GitHub webhook → Tracker event
interface GitHubAcl extends ExternalSystemACL<GitHubWebhookPayload, DomainEvent> {
  mapPullRequestEvent(payload: GitHubWebhookPayload): PeerReviewUpdatedEvent;
  mapCommitEvent(payload: GitHubWebhookPayload): CodeCommittedEvent;
}

// Jira ACL for initiative ingestion
interface JiraAcl extends ExternalSystemACL<JiraIssue, InitiativeImport> {
  mapIssueToInitiative(jiraIssue: JiraIssue): InitiativeImport;
}

Part VII — Frontend Architecture

20. Microfrontend Architecture (Following T-002, ADR-0044 & ADR-0045)

Per T-002 and T-002, the frontend is a Microfrontend architecture from day 1 using Module Federation (Vite plugin @module-federation/vite or @originjs/vite-plugin-federation). A Shell Host orchestrates independently deployable React remotes, one per Bounded Context UI. The backend remains a single deployment (Progressive Monolith); only the frontend is physically segmented.

apps/
├── shell-host/                  # Host: layout, global routing, session, MF orchestration
│   ├── src/
│   │   ├── app/                 # Providers, router, error boundaries per remote
│   │   ├── bootstrap/           # Remote registry, dynamic remote loading
│   │   └── layout/              # Shell chrome (nav, header, gate status bar)
│   └── vite.config.ts           # host federation config (remotes registry)
├── mfe-discovery/               # Remote: Discovery module UI
├── mfe-design/                  # Remote: Design module UI
├── mfe-construction/            # Remote: Construction module UI
├── mfe-qa/                      # Remote: QA module UI
├── mfe-release/                 # Remote: Release module UI
├── mfe-governance/              # Remote: Governance + agent orchestration UI
└── mfe-metrics/                 # Remote: Scorecards & analytics UI

packages/                        # Shared libraries consumed by all remotes (Module Federation shared)
├── ui-kit/                      # Shared Design System (prevents visual drift — T-002 §4)
├── contracts/                   # API client types (generated from OpenAPI)
├── auth/                        # usePermission, permission context, route guards
├── event-bus/                   # Client-side event bus for cross-MFE state (T-002 §4)
└── shared-stores/               # Zustand stores for minimal cross-cutting client state

Each remote (mfe-*) internal structure:

mfe-{context}/
├── src/
│   ├── App.tsx                  # Remote entry exposed via Module Federation
│   ├── features/                # Feature components for this bounded context
│   ├── hooks/                   # Context-specific hooks (TanStack Query)
│   ├── routes/                  # Remote-local routing
│   └── bootstrap.tsx            # Standalone bootstrap (remote can run isolated)
└── vite.config.ts               # remote federation config (exposes ./App)

Microfrontend Governance Rules (per T-002):

Concern Rule
Visual consistency All remotes consume the shared ui-kit Design System — no remote ships its own primitives
Shared dependencies react, react-dom, @tanstack/react-query declared as Module Federation shared singletons to avoid duplicate instances
Cross-MFE state Minimal; passed via URL params, storage, or the shared client-side event-bus. No direct remote-to-remote imports
Fault isolation Each remote is wrapped in an error boundary in the Shell Host — a crashing remote must not take down the suite (T-002 §3)
Independent deployment Each remote builds and deploys independently; the Shell Host loads remotes dynamically from a versioned registry
Auth Permission context provided by the Shell Host via the shared auth package; remotes never re-implement authorization

21. State Management — Dual Strategy

State Type Tool Examples
Server State TanStack Query Initiatives, contracts, test results, metrics
Client State Zustand Theme, notification queue, sidebar state, active initiative
// Zustand — client-only state (per ADR-0045)
export const useInitiativeStore = create<{
  activeInitiativeId: string | null;
  setActiveInitiative: (id: string) => void;
}>((set) => ({
  activeInitiativeId: null,
  setActiveInitiative: (id) => set({ activeInitiativeId: id }),
}));

// TanStack Query — server state
export const useInitiative = (id: string) => useQuery({
  queryKey: ['initiative', id],
  queryFn: () => trackerApi.getInitiative(id),
});

22. Permission-Driven UI

// Permission context — consumes canonical permissions from backend
interface PermissionContextValue {
  can: (permission: TrackerPermission) => boolean;
  canAny: (permissions: TrackerPermission[]) => boolean;
  canAll: (permissions: TrackerPermission[]) => boolean;
}

// Protected component
const ApproveButton = ({ initiativeId }) => {
  const { can } = usePermission();
  return can('tracker:initiative:approve')
    ? <Button onClick={() => approve(initiativeId)}>Approve</Button>
    : null;
};

// Route guard
const InitiativeRoute = () => {
  const { can } = usePermission();
  return can('tracker:initiative:read')
    ? <Outlet />
    : <AccessDenied />;
};

Part VIII — API Design

23. REST Endpoints by Context

Context Method Endpoint Description Permission
Discovery
Initiative POST /api/v1/initiatives Submit new initiative tracker:initiative:create
Initiative GET /api/v1/initiatives List initiatives (pipeline) tracker:initiative:read
Initiative GET /api/v1/initiatives/{id} Get initiative detail tracker:initiative:read
Initiative POST /api/v1/initiatives/{id}/approve Approve initiative tracker:initiative:approve
Initiative POST /api/v1/initiatives/{id}/reject Reject initiative tracker:initiative:reject
Initiative POST /api/v1/initiatives/{id}/backlog Generate backlog tracker:initiative:generate-backlog
Design
Blueprint GET /api/v1/initiatives/{id}/blueprint Get blueprint tracker:design:read
Contract POST /api/v1/initiatives/{id}/contracts Submit contract tracker:design:contract:submit
ADR POST /api/v1/initiatives/{id}/adrs Submit ADR tracker:design:adr:submit
Blueprint POST /api/v1/initiatives/{id}/blueprint/approve Approve blueprint tracker:design:approve
Construction
Cycle POST /api/v1/cycles Start implementation cycle tracker:construction:cycle:create
Story GET /api/v1/cycles/{cycleId}/stories List stories tracker:construction:read
Story PATCH /api/v1/stories/{id}/complete Mark complete tracker:construction:task:complete
PeerReview GET /api/v1/stories/{id}/review Get review status tracker:construction:read
Drift GET /api/v1/initiatives/{id}/drift Get drift index tracker:drift:read
QA
TestCycle POST /api/v1/initiatives/{id}/test-cycles Start test cycle tracker:qa:cycle:create
Execution POST /api/v1/test-cycles/{id}/executions Log execution result tracker:qa:execution:create
Defect POST /api/v1/defects Raise defect tracker:qa:defect:create
QualityGate GET /api/v1/initiatives/{id}/quality-gate Get gate status tracker:qa:gate:read
Release
Release POST /api/v1/releases Plan release tracker:release:create
Release POST /api/v1/releases/{id}/authorize Authorize release tracker:release:authorize
Deployment POST /api/v1/releases/{id}/deploy Trigger deployment tracker:release:deploy
Metrics
Scorecard GET /api/v1/initiatives/{id}/scorecard Get DORA/SPACE metrics tracker:scorecard:read
DriftHistory GET /api/v1/initiatives/{id}/drift-history Get drift timeline tracker:drift:read

Part IX — Observability

24. OpenTelemetry Integration

// Trace setup
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const sdk = new NodeSDK({
  serviceName: 'evolith-tracker',
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
  }),
});

// Correlation ID interceptor
@Injectable()
export class CorrelationIdInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const request = context.switchToHttp().getRequest();
    const correlationId = request.headers['x-correlation-id'] ?? generateUUID();
    return next.handle().pipe(
      tap(() => this.logger.log({ correlationId, path: request.path }))
    );
  }
}

25. Structured Logging

// PII-safe logger — never log: email, password, token, PII
interface LogEntry {
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error';
  correlationId?: string;
  userId?: string;       // Internal ID only, never email
  tenantId?: string;
  action: string;
  durationMs?: number;
  metadata?: Record<string, unknown>;
}

Part X — Deployment Architecture

26. Docker Compose (Local)

version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_MULTIPLE_DATABASES: tracker_discovery,tracker_design,tracker_construction,tracker_qa,tracker_release,tracker_governance,tracker_artifacts,tracker_metrics,tracker_integration,tracker_audit
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  api:
    build:
      context: .
      dockerfile: Dockerfile.api
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://evolith:localdev@postgres:5432
      REDIS_URL: redis://redis:6379
      OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318

  web:
    build:
      context: .
      dockerfile: Dockerfile.web
    ports:
      - "5173:5173"

27. Kubernetes (Helm)

# Conceptual Helm values structure
api:
  replicaCount: 2
  image:
    repository: evolith/tracker-api
    tag: latest
  env:
    DATABASE_URL: postgresql://${DB_HOST}:5432/tracker
    OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318

web:
  replicaCount: 2
  image:
    repository: evolith/tracker-web
    tag: latest

postgres:
  enabled: true
  storageSize: 50Gi

ingress:
  enabled: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod

Part XI — Equivalence Matrix (UMS Technical Patterns)

UMS Pattern Purpose Tracker Adaptation Implementation
Aggregate Root + Entity Transactional boundary Same — Small Aggregates pattern class Initiative extends AggregateRoot
Value Object Immutable descriptor Same readonly struct Code {}
Domain Event State change notification Same interface DomainEvent
Repository (Port + Adapter) Persistence abstraction Same IInitiativeRepository + TypeOrmInitiativeRepository
Command + Handler Write operation Same SubmitInitiativeCommand + SubmitInitiativeHandler
Query + Handler Read operation Same GetInitiativeStatusQuery + GetInitiativeStatusHandler
Result Error handling without exceptions Same class Result<T, TError>
Transactional Outbox Eventual consistency Same OutboxMessage table + processor
Domain Service Stateless logic Same DriftDetectionService
Requirement Checklist Dynamic workflow validation Same Injected via WorkflowEngine port
External Reference VO External system linking Same ExternalReference VO
Tenant Context Multi-tenancy isolation Same TenantContextMiddleware + RLS
UMS SDK Authorization UMS direct integration UmsSecurityAdapter
In-Memory Event Bus Internal messaging Same InMemoryDomainEventBus

Part XII — Traceability Matrix

Capability Source Document Evolith Core Rule Implementation
Initiative lifecycle PRD UC-001, Discovery DDD SDLC Phase Gate standard Initiative aggregate
Phase Gate enforcement PRD BR-001 to BR-003 Phase Gate definition PhaseGateEvaluator service
Architecture Drift detection PRD UC-003, Construction DDD ADR on drift DriftDetectionService
UMS AuthN integration PRD Out of Scope, UX Concept UMS IdP contract UmsSecurityAdapter
CLI as first-class interface PRD UC-007, UX Concept CLI standard evolith CLI package
MCP Server integration PRD UC-008, UX Concept MCP protocol @evolith/tracker-mcp package
BMAD agent orchestration PRD EPIC-005, EPIC-006 Agent governance AgentOrchestrationService
DORA/SPACE metrics PRD Scorecards, Product Goals SPACE/DORA standards MetricsComputationService
Re-Do Flow PRD UC-005, Release DDD Contingency rule ReDoFlowEngine
Evidence generation PRD Artifact standard Evidence artifact spec EvidenceRecord aggregate
Core synchronization PRD Governance Core versioning CoreIntegrationService
Multi-schema PostgreSQL C4 Topology, this document Schema-per-domain 10 PostgreSQL schemas
OpenAPI contract-first PRD EPIC-001 API standard OpenAPI 3.0 specs per module

Part XIII — Gaps and Decisions Pending

Gap Impact Recommendation
UMS Authorization Graph schema not inspected Cannot finalize permission mapping Inspect UMS OpenAPI before implementation
Evolith Core SDK not available Cannot validate integration design Propose SDK development to Core
.harness integration API unknown Cannot design QA gate integration Define ACL for .harness in tracker-integration
Artifact schema URLs not available Cannot validate artifact definitions Raise request to Core for artifact schema publication
SMART CLI current commands not analyzed Cannot specify CLI coverage gaps Run evolith --help and compare against UC-007

Part XIV — Roadmap

Phase Focus Duration Key Deliverables
Phase 0 Analysis & Foundation S Repository setup, NestJS bootstrap, PostgreSQL schemas, CI pipeline, shared domain primitives
Phase 1 Core Sync + UMS Auth M CoreIntegrationService, UmsSecurityAdapter, permission mapping, auth guards
Phase 2 Discovery + Design Modules M Initiative, Backlog, Epic, UserStory aggregates; TechnicalBlueprint, Contract, ADR aggregates
Phase 3 Construction + QA Modules M ImplementationCycle, TechnicalStory, PeerReview; TestCycle, TestExecution, Defect
Phase 4 Release + Governance M ReleasePackage, DeploymentRecord; SatelliteProduct, SDLCExecution
Phase 5 CLI + MCP Server M evolith CLI package, MCP tools, agent orchestration
Phase 6 Metrics + Scorecards M DORA/SPACE computation, drift alerts, dashboards
Phase 7 Integrations (GitHub, Jira) M ACL adapters, webhook handlers
Phase 8 Hardening + Release M Security audit, performance testing, observability, Helm charts, E2E tests

References