# Maat Domain — Technical Specifications

> **Maat** — Portfolio Intelligence and Business Management Platform

This document specifies what is **implemented** in the Maat domain. It is
grounded in the source under `libs/maat/*`, `apps/maat/*`, and
`libs/contracts/maat/`. Every entity, field, enum value, event, endpoint,
permission, and count below was read from source:
`libs/maat/core/src/ {organization,event-bus,redis-namespace}.ts`,
`libs/maat/*/src/index.ts`, `libs/maat/finance/src/v2-persistence-ledger.ts`,
`libs/maat/strategy/src/v2-fighting-game-live-ops-calendar.ts`,
`libs/maat/dashboard/src/v2-balance-dashboard.ts`,
`libs/maat/negotiation-intelligence/src/procurement-program.ts`,
`libs/maat/knowledge/src/knowledge-graph-neo4j-schema.ts`,
`libs/maat/sdk/src/typed-client.ts`,
`apps/maat/api-gateway/src/{app,openapi/spec,middleware/api-versioning, realtime/websocket-server}.ts`,
and the `apps/maat/*` route trees. Where a capability is present only as a
scaffold or is named in a phase backlog, it is labelled accordingly.

---

## 1. Domain Object Model (`@maat/core`)

The foundational types live in `libs/maat/core/src/organization.ts`. This single
file defines the entire shared domain vocabulary for the Maat platform: branded
ID types that prevent accidental cross-type assignment, frozen enum value
arrays, Zod schemas for every entity, and a complete set of constructor,
serializer, and type guard functions. There is no ORM — all entities are
in-memory value objects that can be validated, constructed, and serialized
without a database.

For each entity, `organization.ts` follows a consistent pattern:

- A `*Draft` interface accepts loosely typed inputs (strings, partial objects).
- `create<Entity>(draft)` validates and normalises the draft, throwing on
  invalid input.
- `create<Entity>WithDefaults(overrides?)` deep-merges a `DeepPartial` over a
  built-in default object, making test setup concise.
- `serialize<Entity>(value)` / `deserialize<Entity>(serialized)` provide JSON
  round-trip helpers.
- `is<Entity>(value)` is an `unknown`-narrowing type guard.

### 1.1 Branded Identifier Types

To prevent accidental cross-type mixing at compile time (passing an `AgentId`
where an `OrganizationId` is expected), every entity identifier is a branded
string type. Each type enforces a known prefix pattern and has a dedicated
constructor function. The table below lists the 13 entity ID types and their
regex patterns, all case-insensitive.

`organization.ts` defines a `Brand<T, B>` helper and these branded string IDs,
each with a regex pattern and a `create*Id` constructor:

| Type                 | Pattern (case-insensitive)            |
| -------------------- | ------------------------------------- |
| `OrganizationId`     | `org_[a-z0-9][a-z0-9_-]{2,62}`        |
| `OperatingCompanyId` | `opco_[a-z0-9][a-z0-9_-]{2,62}`       |
| `BusinessUnitId`     | `bu_[a-z0-9][a-z0-9_-]{2,62}`         |
| `MarketId`           | `market_[a-z0-9][a-z0-9_-]{2,62}`     |
| `CompetitorId`       | `competitor_[a-z0-9][a-z0-9_-]{2,62}` |
| `ProjectId`          | `project_[a-z0-9][a-z0-9_-]{2,62}`    |
| `KPIId`              | `kpi_[a-z0-9][a-z0-9_-]{2,62}`        |
| `RiskId`             | `risk_[a-z0-9][a-z0-9_-]{2,62}`       |
| `AgentId`            | `agent_[a-z0-9][a-z0-9_-]{2,62}`      |
| `ScenarioId`         | `scenario_[a-z0-9][a-z0-9_-]{2,62}`   |
| `DashboardId`        | `dashboard_[a-z0-9][a-z0-9_-]{2,62}`  |
| `AlertId`            | `alert_[a-z0-9][a-z0-9_-]{2,62}`      |
| `AuditEventId`       | `audit_[a-z0-9][a-z0-9_-]{2,62}`      |

`ISODateString` (`YYYY-MM-DD`) and `ISODateTimeString` (UTC ISO-8601) are also
branded; `isISODateString`, `createISODateString`, `isISODateTimeString`, and
`createISODateTimeString` validate and construct them.

### 1.2 Enums and Literal-Union Value Arrays

Rather than TypeScript `enum` declarations, every enumerated set is expressed as
a frozen `*_VALUES` `as const` array with a derived union type. This pattern
makes the full value set available at runtime (for validation, UI dropdowns, and
API responses) while also providing compile-time type narrowing.

The following table lists all value arrays and their members.

| Value array                                | Members                                                                                                                                                           |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LEGAL_ENTITY_TYPE_VALUES`                 | `HOLDING_COMPANY`, `CONGLOMERATE`, `PRIVATE_LIMITED`, `PUBLIC_LIMITED`, `PARTNERSHIP`, `COOPERATIVE`, `NON_PROFIT`, `STATE_OWNED`                                 |
| `LEGAL_STRUCTURE_MODEL_VALUES`             | `SINGLE_ENTITY`, `HOLDING_WITH_SUBSIDIARIES`, `MULTI_HOLDING`, `FEDERATED`                                                                                        |
| `OWNERSHIP_MODEL_VALUES`                   | `PRIVATELY_HELD`, `PUBLICLY_TRADED`, `STATE_OWNED`, `MIXED`                                                                                                       |
| `OPERATING_COMPANY_INDUSTRY_SECTOR_VALUES` | `AGRICULTURE`, `MANUFACTURING`, `ENERGY`, `LOGISTICS`, `FINANCIAL_SERVICES`, `TECHNOLOGY`, `HEALTHCARE`, `CONSUMER_GOODS`, `REAL_ESTATE`, `PROFESSIONAL_SERVICES` |
| `OPERATING_COMPANY_BOARD_ROLE_VALUES`      | `CHAIRPERSON`, `EXECUTIVE_DIRECTOR`, `NON_EXECUTIVE_DIRECTOR`, `INDEPENDENT_DIRECTOR`                                                                             |
| `MARKET_GEOGRAPHIC_REGION_VALUES`          | `GHANA`, `WEST_AFRICA`, `PAN_AFRICAN`, `GLOBAL`                                                                                                                   |
| `COMPETITOR_THREAT_LEVEL_VALUES`           | `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`                                                                                                                               |
| `PROJECT_STATUS_VALUES`                    | `PROPOSED`, `APPROVED`, `IN_PROGRESS`, `COMPLETED`, `CANCELLED`                                                                                                   |
| `KPI_METRIC_TYPE_VALUES`                   | `FINANCIAL`, `OPERATIONAL`, `STRATEGIC`, `ESG`                                                                                                                    |
| `KPI_MEASUREMENT_FREQUENCY_VALUES`         | `REAL_TIME`, `DAILY`, `WEEKLY`, `MONTHLY`, `QUARTERLY`, `ANNUALLY`                                                                                                |
| `KPI_TREND_DIRECTION_VALUES`               | `INCREASING`, `DECREASING`, `STABLE`                                                                                                                              |
| `KPI_THRESHOLD_COMPARATOR_VALUES`          | `GT`, `GTE`, `LT`, `LTE`                                                                                                                                          |
| `KPI_THRESHOLD_SEVERITY_VALUES`            | `INFO`, `WARNING`, `CRITICAL`                                                                                                                                     |
| `REGULATION_JURISDICTION_VALUES`           | `GHANA`, `ECOWAS`, `AFCFTA`, `INTERNATIONAL`                                                                                                                      |
| `RISK_CATEGORY_VALUES`                     | `MARKET`, `CREDIT`, `OPERATIONAL`, `REGULATORY`, `POLITICAL`, `ENVIRONMENTAL`, `SUPPLY_CHAIN`                                                                     |
| `RISK_REVIEW_CADENCE_VALUES`               | `WEEKLY`, `MONTHLY`, `QUARTERLY`, `SEMI_ANNUALLY`, `ANNUALLY`                                                                                                     |
| `AGENT_CLASS_VALUES`                       | `STRATEGY`, `ENGINEERING`, `FINANCE`, `OPERATIONS`, `RESEARCH`, `COMPLIANCE`                                                                                      |
| `AGENT_TASK_PRIORITY_VALUES`               | `LOW`, `MEDIUM`, `HIGH`, `URGENT`                                                                                                                                 |
| `AGENT_TASK_STATUS_VALUES`                 | `QUEUED`, `IN_PROGRESS`, `BLOCKED`                                                                                                                                |
| `SCENARIO_CATEGORY_VALUES`                 | `BASE_CASE`, `BEST_CASE`, `WORST_CASE`, `CUSTOM`                                                                                                                  |
| `DASHBOARD_OWNER_TYPE_VALUES`              | `USER`, `AGENT`                                                                                                                                                   |
| `DASHBOARD_LAYOUT_TYPE_VALUES`             | `GRID`, `FREEFORM`                                                                                                                                                |
| `DASHBOARD_DATA_SOURCE_MODE_VALUES`        | `PULL`, `PUSH`                                                                                                                                                    |
| `DASHBOARD_PERMISSION_SUBJECT_TYPE_VALUES` | `USER`, `ROLE`, `AGENT`                                                                                                                                           |
| `DASHBOARD_PERMISSION_LEVEL_VALUES`        | `VIEWER`, `EDITOR`, `OWNER`                                                                                                                                       |
| `ALERT_SEVERITY_VALUES`                    | `INFO`, `WARNING`, `CRITICAL`, `EMERGENCY`                                                                                                                        |
| `ALERT_ACTION_PRIORITY_VALUES`             | `LOW`, `MEDIUM`, `HIGH`, `URGENT`                                                                                                                                 |
| `ALERT_ESCALATION_CHANNEL_VALUES`          | `EMAIL`, `SMS`, `SLACK`, `PAGER`                                                                                                                                  |
| `ALERT_ACKNOWLEDGMENT_STATUS_VALUES`       | `UNACKNOWLEDGED`, `ACKNOWLEDGED`                                                                                                                                  |
| `AUDIT_EVENT_TYPE_VALUES`                  | `CREATE`, `UPDATE`, `DELETE`, `APPROVE`, `REJECT`, `ASSIGN`, `ESCALATE`, `ACKNOWLEDGE`, `RESOLVE`                                                                 |
| `AUDIT_EVENT_ACTOR_TYPE_VALUES`            | `USER`, `AGENT`                                                                                                                                                   |
| `AUDIT_COMPLIANCE_CLASSIFICATION_VALUES`   | `PUBLIC`, `INTERNAL`, `CONFIDENTIAL`, `RESTRICTED`                                                                                                                |
| `SYNERGY_CATEGORY_VALUES`                  | `REVENUE`, `COST`, `KNOWLEDGE`, `OPERATIONAL`                                                                                                                     |

### 1.3 Core Entities

The 15 root entities are `Organization`, `OperatingCompany`, `BusinessUnit`,
`Market`, `Competitor`, `Project`, `KPI`, `Regulation`, `Risk`, `Agent`,
`Scenario`, `Dashboard`, `Alert`, `AuditEvent`, and `Synergy`. Each has a
matching `*Draft` interface (loosely typed inputs accepted by the `create*`
constructor) and an exported Zod schema. The detailed field specifications
follow.

#### `Organization`

The top-level entity representing the holding group that owns the portfolio
companies.

| Field                          | Type                             | Meaning                                                                                                                                                                                          |
| ------------------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                           | `OrganizationId`                 | Branded organization identifier                                                                                                                                                                  |
| `name`                         | `string`                         | Non-empty trimmed name                                                                                                                                                                           |
| `legalEntityStructure`         | `LegalEntityStructure`           | Entity type, structure model, ownership model, and `registrations` (≥ 1, exactly one `isPrimary`)                                                                                                |
| `incorporationJurisdiction`    | `IncorporationJurisdiction`      | `countryCode`, optional `stateOrRegion`/`city`, `legalRegime`, `regulator`                                                                                                                       |
| `foundingDate`                 | `ISODateString`                  | Incorporation date                                                                                                                                                                               |
| `missionStatement`             | `string`                         | Non-empty mission text                                                                                                                                                                           |
| `operatingCompanies`           | `OrganizationOperatingCompany[]` | ≥ 1 references: `companyId`, `name`, `strategicRole`, `ownershipPercent`                                                                                                                         |
| `consolidatedFinancialSummary` | `ConsolidatedFinancialSummary`   | `currencyCode`, `fiscalYear`, `revenue`, `grossProfit`, `ebitda`, `netIncome`, `totalAssets`, `totalLiabilities`, `shareholdersEquity`, `operatingCashFlow`, `freeCashFlow`, `debtToEquityRatio` |

#### `OperatingCompany`

Represents one of the seven portfolio companies. Captures Ghana-specific
regulatory identifiers (GRA TIN, SSNIT employer number) alongside standard
company metadata, reflecting the platform's Ghana-first compliance posture.

| Field                        | Type                                    | Meaning                                                                       |
| ---------------------------- | --------------------------------------- | ----------------------------------------------------------------------------- |
| `id`                         | `OperatingCompanyId`                    | Branded company identifier                                                    |
| `name`                       | `string`                                | Company name                                                                  |
| `industrySector`             | `OperatingCompanyIndustrySector`        | Sector classification                                                         |
| `isicClassificationCode`     | `string`                                | ISIC code (section letter + 2–4 digits)                                       |
| `ghanaRegistrationNumber`    | `string`                                | Registrar of Companies number (prefix `C`/`CS`/`BN`/`CG`/`RGD` + 3–12 digits) |
| `graTaxIdentificationNumber` | `string`                                | GRA TIN (leading letter + 8–12 digits)                                        |
| `ssnitEmployerNumber`        | `string`                                | SSNIT employer number (5–12 digits)                                           |
| `headquartersLocation`       | `OperatingCompanyHeadquartersLocation`  | `countryCode`, optional region, `city`, optional address/postal code          |
| `subsidiaries`               | `OperatingCompanySubsidiaryReference[]` | `subsidiaryCompanyId`, `name`, `ownershipPercent`                             |
| `ceo`                        | `OperatingCompanyExecutiveReference`    | `executiveId`, `fullName`, `title`, `appointedAt`                             |
| `boardMembers`               | `OperatingCompanyBoardMember[]`         | `memberId`, `fullName`, `role`, `appointedAt`, optional `termEndAt`           |
| `annualRevenueRange`         | `OperatingCompanyAnnualRevenueRange`    | `currencyCode`, `fiscalYear`, `minimum`, `maximum`                            |

#### `BusinessUnit`

A division within an operating company, with its own product lines, factory
locations, P&L summary, and workforce count.

Fields: `id`, `parentCompanyId`, `name`, `graTaxIdentificationNumber`,
`ssnitEmployerNumber`, `productLines[]` (`name`, `category`,
`annualOutputUnits`), `factoryLocations[]` (`siteCode`, `countryCode`, region,
`city`, `primaryProducts[]`), `employeeCount`, `capacityUtilizationPercent`, and
`profitAndLossSummary` (`currencyCode`, `fiscalYear`, `revenue`,
`costOfGoodsSold`, `grossProfit`, `operatingExpenses`, `operatingProfit`,
`netProfit`).

#### `Market`

Describes a market segment that portfolio companies operate in or are evaluating
for entry, including size estimates (TAM/SAM/SOM) and a regulatory complexity
index.

Fields: `id`, `name`, `geographicRegion`, `sectorClassification`,
`sizeEstimates` (`currencyCode`, `referenceYear`, `tam`, `sam`, `som`),
`annualGrowthRatePercent`, `competitiveIntensityScore`,
`regulatoryComplexityIndex`.

#### `Competitor`

Tracks a named competitor with its market share, strengths, weaknesses, a
timestamped timeline of recent moves, a threat level, and an intelligence
freshness timestamp so stale competitive data is always visible.

Fields: `id`, `name`, `marketSharePercent`, `strengths[]`, `weaknesses[]`,
`recentMovesTimeline[]` (`happenedAt`, `headline`, `summary`), `threatLevel`,
`intelligenceFreshnessTimestamp`.

#### `Project`

A tracked initiative owned by a business unit, with budget allocation, actual
spend, milestones, assigned agents, and a reference to the risk register.

Fields: `id`, `owningBusinessUnitId`, `name`, `description`, `status`,
`budgetAllocation` (`currencyCode`, `amount`), `actualSpend`,
`timelineMilestones[]` (`milestoneId`, `name`, `targetDate`, optional
`completionDate`), `assignedAgents[]` (`agentId`, `role`, `allocationPercent`),
`riskRegisterReference`.

#### `KPI`

A Key Performance Indicator with a target value, actual value, measurement
frequency, trend direction, and a set of threshold alerts that fire when the
metric crosses configured boundaries.

Fields: `id`, `name`, `description`, `metricType`, `targetValue` (`value`,
`unit`), `actualValue`, `measurementFrequency`, `trendDirection`,
`thresholdAlerts[]` (`alertId`, `thresholdValue`, `comparator`, `severity`,
`message`), `responsibleBusinessUnitId`.

#### `Regulation`

A regulatory requirement from a specific body, in a specific jurisdiction, with
compliance requirements, associated penalties, and a list of affected business
units. Note that `Regulation` has no branded ID — it is identified by
`jurisdictionCode` + `regulationIdentifier`.

Fields: `jurisdiction`, `regulatoryBodyName`, `regulationIdentifier`,
`effectiveDate`, `complianceRequirements[]` (`requirementId`, `description`,
`isMandatory`, optional `dueDate`), `penaltyStructure` (optional `currencyCode`,
optional min/max monetary penalty, `nonMonetaryPenalties[]`),
`affectedBusinessUnitIds[]`.

#### `Risk`

A risk entry in a risk register, with a composite risk score derived from
likelihood and impact scores, mitigation strategies, an owner reference, and a
review schedule with a next-review date.

Fields: `id`, `category`, `likelihoodScore`, `impactScore`,
`compositeRiskScore`, `mitigationStrategies[]`, `ownerReference` (`ownerId`,
`ownerName`, `ownerRole`), `reviewSchedule` (`cadence`, `nextReviewDate`,
optional `lastReviewedDate`).

#### `Agent`

An AI agent registration record. Each agent has a class (strategy, engineering,
finance, operations, research, or compliance), a capability list validated
against the `AGENT_CAPABILITY_MATRIX`, assigned tools, a memory context window
size, a performance score, and a current task queue.

Fields: `id`, `agentClass`, `name`, `capabilities[]`, `assignedTools[]`,
`memoryContextWindow`, `performanceScore`, `currentTaskQueue[]`
(`AgentTaskQueueItem`: `taskId`, `title`, `priority`, `status`). The constructor
checks each capability against `AGENT_CAPABILITY_MATRIX`, a per-`AgentClass` map
of allowed capability strings.

#### `Scenario`

A named simulation scenario with probabilistic weighting, outcome projections (a
record of metric names to numeric values), and sensitivity parameters that bound
the key assumptions.

Fields: `id`, `name`, `description`, `scenarioCategory`, `assumptions[]`,
`timeHorizonMonths`, `probabilityWeight`, `simulatedOutcomes` (record of
`string → number`), `sensitivityParameters[]` (`parameterName`, `baseValue`,
`minimumValue`, `maximumValue`).

#### `Dashboard`

A configurable dashboard entity owned by a user or agent, with a grid or
freeform layout, widget definitions with pixel positions, data-source bindings,
refresh interval, and per-subject access permissions.

Fields: `id`, `ownerReference` (`ownerId`, `ownerType`, `displayName`),
`layoutConfiguration` (`layoutType`, `columns`, `rowHeight`),
`widgetDefinitions[]` (`widgetId`, `widgetType`, `title`, `position`
`{x,y,width,height}`), `dataSourceBindings[]` (`bindingId`, `widgetId`,
`sourceKey`, `mode`), `refreshIntervalSeconds`, `accessPermissions[]`
(`subjectType`, `subjectId`, `permissionLevel`), `lastViewedTimestamp`.

#### `Alert`

A system alert representing a condition requiring attention, with an escalation
chain that automatically escalates to successively higher-level targets after
configurable timeout windows.

Fields: `id`, `severity`, `category`, `title`, `description`, `sourceSystem`,
`affectedEntities[]` (`entityType`, `entityId`), `recommendedActions[]`
(`actionId`, `description`, `priority`, optional `ownerId`),
`acknowledgmentStatus`, `escalationChain[]` (`level`, `target`, `channel`,
`triggerAfterMinutes`).

#### `AuditEvent`

An immutable audit record capturing who did what to which entity, with
before/after state snapshots, a compliance classification, and a correlation ID
for distributed tracing.

Fields: `id`, `eventType`, `actor` (`actorType`, `actorId`, optional
`actorDisplayName`), `targetEntity` (`entityType`, `entityId`, optional
`entityName`), `actionPerformed`, `previousStateSnapshot`, `newStateSnapshot`,
`timestamp`, `correlationId`, `complianceClassification`.

#### `Synergy`

A quantified inter-company synergy opportunity, linking a source business unit
to a target business unit with an estimated annual value, a realization
timeline, a probability score, and prerequisite dependencies. Note that
`Synergy` has no branded ID.

Fields: `sourceBusinessUnitId`, `targetBusinessUnitId`, `category`,
`estimatedAnnualValue` (`currencyCode`, `amount`, `referenceFiscalYear`),
`realizationTimeline` (`plannedStartDate`, `targetRealizationDate`),
`probabilityScore`, `dependencyPrerequisites[]` (`code`, `description`).

### 1.4 Constructors, Serialization, and Type Guards

For each of the 15 entities, `organization.ts` exports the following — all must
be present when adding a new entity:

- `create<Entity>(draft)` — validates and normalises a `*Draft`, throwing on
  invalid input;
- `create<Entity>WithDefaults(overrides?)` — deep-merges a `DeepPartial` over a
  built-in default object;
- `serialize<Entity>(value)` / `deserialize<Entity>(serialized)` — JSON
  round-trip helpers;
- `is<Entity>(value)` — `unknown`-narrowing type guard.

The Ghana-identifier schemas `GhanaCompanyRegistrationNumberSchema`,
`GRATaxIdentificationNumberSchema`, and `SSNITEmployerNumberSchema` are exported
for reuse by any library that needs to validate these identifiers independently.

---

## 2. Domain Event System (`@maat/core`)

Defined in `libs/maat/core/src/event-bus.ts`. There is **no Redis-backed bus**;
the implementation is a typed in-process bus. Events are the mechanism by which
domain activities (market signals, agent completions, strategy decisions,
compliance alerts, simulation progress) notify other parts of the system without
creating direct library dependencies between them.

### 2.1 Event Types

All domain events conform to exactly five types, each with its own Zod payload
schema. `MaatDomainEventTypeSchema` is a Zod enum of these five values:

| Event type                | Payload schema                         |
| ------------------------- | -------------------------------------- |
| `MarketIntelligenceEvent` | `MarketIntelligenceEventPayloadSchema` |
| `AgentTaskEvent`          | `AgentTaskEventPayloadSchema`          |
| `StrategyDecisionEvent`   | `StrategyDecisionEventPayloadSchema`   |
| `ComplianceAlertEvent`    | `ComplianceAlertEventPayloadSchema`    |
| `SimulationStateEvent`    | `SimulationStateEventPayloadSchema`    |

### 2.2 Base Envelope

Every event extends a shared base envelope, ensuring consistent tracing metadata
regardless of event type. `BaseMaatEventSchema` requires: `eventId` (UUID),
`eventType`, `schemaVersion` (literal `1`), `organizationId`, `source`,
`correlationId`, optional `causationId`, optional `traceId`, and `occurredAt`
(ISO-8601 with offset). `MaatDomainEventSchema` is a discriminated union on
`eventType`.

### 2.3 Event Payloads

The five event payload schemas carry the following domain-specific fields. Note
the bounded numeric fields (confidence, impact scores) and the discriminated
status enums — these enforce domain invariants at the schema level.

| Event                     | Payload fields                                                                                                                                                                                                                                                                                                           |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MarketIntelligenceEvent` | `marketId`, `signalType` (`PRICE_MOVEMENT` / `DEMAND_SHIFT` / `SUPPLY_DISRUPTION` / `COMPETITOR_ACTIVITY` / `REGULATORY_CHANGE`), `severity` (`INFO`/`WARNING`/`CRITICAL`), `confidence` (0–1), `summary`, `affectedSectors[]`, `metrics` (record of `string → number`)                                                  |
| `AgentTaskEvent`          | `taskId`, `agentId`, `taskType` (`INTELLIGENCE_SCAN` / `STRATEGY_SIMULATION` / `FINANCIAL_ANALYSIS` / `SUPPLY_CHAIN_OPTIMIZATION` / `COMPLIANCE_REVIEW`), `status` (`QUEUED`/`ASSIGNED`/`IN_PROGRESS`/`BLOCKED`/`COMPLETED`/`FAILED`), `priority` (`LOW`/`MEDIUM`/`HIGH`/`URGENT`), optional `dueAt`, optional `details` |
| `StrategyDecisionEvent`   | `decisionId`, `strategyId`, `decisionType` (`INVESTMENT` / `DIVESTMENT` / `EXPANSION` / `PARTNERSHIP` / `RESTRUCTURE` / `RISK_MITIGATION`), `rationale`, `confidence` (0–1), `expectedImpactScore` (−100…100), `approvedBy[]` (≥ 1)                                                                                      |
| `ComplianceAlertEvent`    | `alertId`, `jurisdiction` (`GHANA`/`ECOWAS`/`AFCFTA`/`INTERNATIONAL`), `regulationCode`, `severity` (`WARNING`/`CRITICAL`/`EMERGENCY`), `title`, `description`, optional `dueAt`, `impactedBusinessUnits[]`                                                                                                              |
| `SimulationStateEvent`    | `simulationId`, `scenarioId`, `runId`, `state` (`INITIALIZING`/`RUNNING`/`PAUSED`/`COMPLETED`/`FAILED`), `progressPercent` (0–100), `horizonMonths` (positive int), `summary`                                                                                                                                            |

### 2.4 Bus API

The `InMemoryMaatEventBus` class and its supporting utilities form the complete
bus API:

- `MAAT_EVENT_SCHEMAS` — record mapping each event type to its Zod schema.
- `parseMaatDomainEvent(event)` — validates an unknown payload, routing by
  `eventType` to the matching schema.
- `createMaatEvent(draft)` — fills `eventId` (`randomUUID()`), `schemaVersion`
  (`1`), and `occurredAt` (`new Date().toISOString()`) defaults, then validates.
- `InMemoryMaatEventBus` — class with `publish(input)` (parses then dispatches),
  `subscribe(eventType, handler)`, `subscribeAll(handler)`,
  `getSubscriberCount(eventType?)`, and `clearSubscriptions()`. Each
  `subscribe*` call returns an unsubscribe function. Handlers run via
  `Promise.allSettled`; if any reject, dispatch throws an `AggregateError`.

---

## 3. Redis Namespace Builder (`@maat/core`)

`libs/maat/core/src/redis-namespace.ts` provides a pure key-string builder; it
does not open a Redis connection. Its purpose is to centralize the key-prefix
convention so that all services are guaranteed to use consistent, non-colliding
namespace strings when a Redis integration is eventually wired in.

`createMaatRedisNamespacePrefixes(options?)` accepts `basePrefix` (default
`maat`), `environment`, and `separator` (default `:`), and returns
`MaatRedisNamespacePrefixes`. When an `environment` is provided, the root prefix
becomes `maat:<environment>` — keeping dev, staging, and production namespaces
separate on a shared Redis instance.

| Prefix field        | Default value                                                 |
| ------------------- | ------------------------------------------------------------- |
| `root`              | `maat` (or `maat:<environment>` when an environment is given) |
| `agentState`        | `maat:agent:state`                                            |
| `intelligenceCache` | `maat:intelligence:cache`                                     |
| `strategyResults`   | `maat:strategy:results`                                       |
| `dashboardState`    | `maat:dashboard:state`                                        |
| `rateLimiting`      | `maat:rate:limit`                                             |

`createMaatRedisKeyBuilder(prefixes?)` returns a `MaatRedisKeyBuilder` with
`agentState`, `intelligenceCache`, `strategyResult`, `dashboardState`, and
`rateLimit` key-composing functions. `assertUniqueNamespacePrefixes(prefixes)`
throws if any two prefixes collide. `MAAT_REDIS_NAMESPACES` and
`MAAT_REDIS_KEYS` are the default-configured singletons.

---

## 4. Library Surface

`libs/maat/` contains **19 libraries**, all published as `@maat/<name>`. Each
library's `src/index.ts` exports a `<NAME>_LIBRARY` package-name constant plus
the per-feature modules below. The implementation modules are pure TypeScript;
no library imports a database driver (`pg`, `drizzle`, `neo4j-driver`,
`redis`/`ioredis`). The only external runtime SDK is `@anthropic-ai/sdk`,
imported by `@maat/intelligence` in `world-model-simulator.ts` and
`report-generation-pipeline.ts`.

The table below shows status and module counts as read from each library's
`src/index.ts`. A "scaffold" status means the library exists with a
`project.json` and package.json but its source files contain minimal stubs — it
is a placeholder for planned work, not a reduced-size implementation.

| Library                          | Status      | Module count (`index.ts`)                                  |
| -------------------------------- | ----------- | ---------------------------------------------------------- |
| `@maat/core`                     | implemented | 3 modules (`event-bus`, `organization`, `redis-namespace`) |
| `@maat/agents`                   | implemented | 28 modules                                                 |
| `@maat/capital`                  | implemented | 15 modules                                                 |
| `@maat/compliance`               | implemented | 20 modules                                                 |
| `@maat/digital-twin`             | implemented | 20 modules                                                 |
| `@maat/finance`                  | implemented | 21 modules                                                 |
| `@maat/integrations`             | implemented | 7 modules (one per portfolio company)                      |
| `@maat/intelligence`             | implemented | 30 modules                                                 |
| `@maat/knowledge`                | implemented | 21 modules                                                 |
| `@maat/projects`                 | implemented | 15 modules                                                 |
| `@maat/reporting`                | implemented | 15 modules                                                 |
| `@maat/risk`                     | implemented | 15 modules                                                 |
| `@maat/sdk`                      | implemented | 10 modules                                                 |
| `@maat/strategy`                 | implemented | 21 modules                                                 |
| `@maat/supply-chain`             | implemented | 20 modules                                                 |
| `@maat/workforce`                | implemented | 15 modules                                                 |
| `@maat/dashboard`                | scaffold    | 1 module — only the V2 balance-dashboard contract (§7)     |
| `@maat/negotiation-intelligence` | scaffold    | 1 module — `procurement-program` (§4.1)                    |

> Note: `@maat/dashboard` is **not** a general dashboard aggregation layer. Its
> only source file beyond the V2 contract is a `.gitkeep`. Dashboard _entity_
> types (`Dashboard`, widgets, layouts, permissions) live in `@maat/core`.

### 4.1 `@maat/negotiation-intelligence` — Procurement Program (Phase 179.7.1)

Defined in `negotiation-intelligence/src/procurement-program.ts`. This module
seeds the Concordia shared bargaining substrate with Maat's procurement-program
configuration: the tracks Maat negotiates under, the levers it uses, and the
approval thresholds that gate high-value decisions. The agent mediation
behaviour itself is still planned for a later phase.

It exports:

- `ProcurementTrackSchema` — enum of procurement tracks.
- `ProcurementLeverSchema` — enum of negotiation levers.
- `ApprovalThresholdSchema` — object with an approval threshold definition.
- `ProcurementProgramSchema` — the full program object.
- `findApprovalThreshold(...)` — selects the threshold applicable to a value.
- `toMaatExtensionDefaults(program)` — projects a program into Concordia
  extension defaults.

---

## 5. API Gateway (`apps/maat/api-gateway`)

`apps/maat/api-gateway` is a **Hono** application (not Fastify). It is a
JWT-authenticated reverse-dispatch gateway that sits in front of per-domain
upstream services, providing a single authenticated entry point for all Maat API
consumers. Rather than each downstream service managing its own auth and
organization access enforcement, the gateway handles both centrally. Source:
`apps/maat/api-gateway/src/app.ts`.

### 5.1 Authentication and Authorization

- `createAuthMiddleware` verifies a `Bearer` JWT with `HS256` using
  `MAAT_JWT_SECRET` (falls back to a development secret).
- `MaatJwtClaimsSchema` requires `sub`, `orgId`, `roles[]`, `permissions[]`, and
  optional `exp`/`iat`.
- `MaatPermissionSchema` enumerates 23 permission strings: `gateway:admin` plus
  `read`/`write` pairs for `intelligence`, `strategy`, `agents`, `finance`,
  `supply-chain`, `digital-twin`, `compliance`, `knowledge`, `dashboard`,
  `simulation`, and `worker`.
- `requirePermission(permission)` allows the request if the caller holds the
  permission or the `platform-admin` role.
- `ensureOrganizationAccess` rejects cross-org access unless the caller is a
  `platform-admin`.

### 5.2 Routes

The gateway exposes a set of infrastructure routes directly, plus domain-service
proxy routes for each of the 11 registered services. The infrastructure routes
are:

| Method | Path                                   | Description                                        |
| ------ | -------------------------------------- | -------------------------------------------------- |
| `GET`  | `/health`                              | Gateway liveness                                   |
| `GET`  | `/ready`                               | Readiness; reports `routesRegistered` count        |
| `GET`  | `/api/v1/services`                     | List registered domain services (`gateway:admin`)  |
| `GET`  | `/api/v1/audit/requests`               | Query the request audit trail (`compliance:read`)  |
| `GET`  | `/api/v1/realtime/rooms`               | WebSocket room snapshot (`gateway:admin`)          |
| `POST` | `/api/v1/realtime/dashboard/update`    | Broadcast a dashboard update (`dashboard:write`)   |
| `POST` | `/api/v1/realtime/agents/status`       | Broadcast agent status (`agents:write`)            |
| `POST` | `/api/v1/realtime/market/alert`        | Broadcast a market alert (`intelligence:write`)    |
| `POST` | `/api/v1/realtime/simulation/progress` | Broadcast simulation progress (`simulation:write`) |
| `GET`  | `/api/docs`                            | Swagger UI                                         |
| `GET`  | `/api/docs/openapi.json`               | Generated OpenAPI 3.1 spec                         |

Each of the 11 domain services additionally mounts a router under its base path
(`/api/v1/intelligence`, `/api/v1/strategy`, `/api/v1/agents`,
`/api/v1/finance`, `/api/v1/supply-chain`, `/api/v1/digital-twin`,
`/api/v1/compliance`, `/api/v1/knowledge`, `/api/v1/dashboard`,
`/api/v1/simulation`, `/api/v1/worker`) exposing:

- `GET /health` — service registration status (requires the read permission);
- `POST /dispatch` — forwards an `organizationId`/`path`/`method`/`query`/`body`
  request to the upstream service URL (requires the write permission). Upstream
  URLs come from `MAAT_<SERVICE>_SERVICE_URL` env vars, defaulting to
  `http://maat-<service>:3000`.

### 5.3 Middleware

`apps/maat/api-gateway/src/middleware/` provides three middleware modules:

- `api-versioning.ts` — `MAAT_API_VERSIONS` (currently only `v1`,
  `CURRENT_API_VERSION = 'v1'`); attaches `X-Api-Version*` headers, emits
  `Deprecation`/`Sunset` headers for deprecated versions, and rejects unknown
  versions when `enforceKnownVersions` is set.
- `rate-limit.ts` — `createMaatRateLimitMiddleware` for `/api/v1/*`.
- `request-response-logging.ts` — logging middleware plus
  `InMemoryMaatRequestAuditTrail`, an in-memory audit store with a filterable
  `list({ limit, organizationId, outcome })`.

### 5.4 Realtime WebSocket Server

`apps/maat/api-gateway/src/realtime/websocket-server.ts` defines
`MaatRealtimeWebSocketServer`. This server handles live data delivery for the
five Next.js web apps — pushing dashboard updates, agent status changes, market
alerts, and simulation progress without requiring each app to poll.
`RealtimeChannelSchema` enumerates four channels: `dashboard`, `agents`,
`market-alerts`, `simulation`. Clients send
`subscribe`/`unsubscribe`/`heartbeat` commands; rooms are keyed
`channel:org[:entity]`. Org-scoped access is enforced per room. Broadcast
methods: `broadcastDashboardUpdate`, `broadcastAgentStatus`,
`broadcastMarketAlert`, `broadcastSimulationProgress`.

---

## 6. SDK (`@maat/sdk`)

`libs/maat/sdk/src/typed-client.ts` provides `MaatClient`, a typed client whose
transport is a pluggable `HttpAdapter`. The SDK is designed to work in tests and
integration contexts without a live server: when no adapter is supplied the
client falls back to `MockHttpAdapter`, giving consumers type-safe
request/response shapes even before the real backend is wired.

- `MaatClientConfig` — `baseUrl`, `apiKey`, `environment`
  (`production`/`staging`/`development`/`local`), optional `defaultCurrency`
  (default `GHS`), `defaultTimezone` (default `Africa/Accra`), `timeoutMs`
  (default `30000`), `maxRetries` (default `3`), `modules`, and `debug`.
- Sub-clients on `MaatClient`: `companies` (`CompanyClient`), `finance`
  (`FinanceClient`), `risks` (`RiskClient`), `scenarios` (`ScenarioClient`),
  `reporting` (`ReportingClient`).
- `forCompany(companyId)` returns a `ScopedCompanyClient`; `ping()` probes
  `/api/v1/health`.
- Response envelopes: `ApiResponse<T>` (`data` + `meta`) and
  `PaginatedResponse<T>` (adds `pagination`). `FilterClause` supports operators
  `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `contains`, `startsWith`.

The other SDK modules are `cli-tool`, `react-components`, `webhook-manager`,
`api-key-manager`, `batch-operations`, `sse-streaming`, `plugin-architecture`,
`offline-mode`, and `sdk-docs-generator`.

---

## 7. V2 Cross-Domain Contracts

Maat owns three contracts that serve the separate V2 (fighting-game) program.
They are implemented inside the Maat libraries because Maat provides the
business authority — financial transaction governance, season calendar, and
balance analytics — even in the context of a competitive game. A key invariant
of all three contracts is that **rollback frames must never call Maat
services**: real-time combat simulation is deterministic and must not depend on
external service availability.

### 7.1 Finance Persistence Ledger — `@maat/finance/v2-persistence-ledger`

This subsection defines the **V2 Persistence Ledger Contract**: Maat finance is
the single source of truth for every V2 currency mutation, and the engine holds
only a reconciling cache. The TypeScript authority lives in
`libs/maat/finance/src/v2-persistence-ledger.ts`, the V2 service wrapper is
published as `@v2/maat-finance-ledger` (depending on `@maat/finance` at
`workspace:*`), and the in-engine policy struct is `FV2MaatFinanceLedgerPolicy`
in `V2/ue/Source/V2Persistence/Public/V2PersistenceTypes.h`.

`v2-persistence-ledger.ts` exports `V2_MAAT_FINANCE_LEDGER_CONTRACT_ID`
(`v2.persistence.maat-finance-ledger.v1`) and
`V2_MAAT_FINANCE_OFFLINE_CACHE_MODE`
(`v2-engine-ledger-cache-reconcile-on-sync`).

Transaction types are `grant`, `spend`, `refund`, `adjust`, `migration`.
Decision reasons include `approved-online-authority`, `approved-offline-cache`,
`invalid-transaction-shape`, `region-price-mismatch`, `regulatory-cap-exceeded`,
`refund-reference-required`, and `refund-window-expired`.
`V2MaatFinanceRegionalPricingPolicy` carries region-aware PPP pricing and
regulatory transaction/daily-spend caps.

Required behaviour:

1. Spend decisions verify region-aware PPP pricing.
2. Spend and refund decisions check regulatory caps and refund windows before
   the entry is mirrored.
3. Approved offline entries carry a Maat decision id and reconcile on next
   online sync.
4. Rollback frames never call Maat finance services.

The Unreal side mirrors this in `FV2MaatFinanceLedgerPolicy`, whose
`SourceOfTruthPackageName` defaults to `@maat/finance` and whose
`bRequiresOnlineReconciliation` flag marks cached offline entries that must be
re-validated against Maat on the next online sync. `ApplyMaatFinanceTransaction`
consumes the policy to apply region-priced spend/refund deltas to the engine
currency ledger; it never participates in deterministic combat and never
influences rollback. The reciprocal `@v2/maat-finance-ledger` service composes
`@maat/finance` as its source of truth
(`sourceOfTruthPackageName: MAAT_FINANCE_PACKAGE_NAME`), flags
`inEngineLedgerCache: true` and `reconcileOnNextOnlineSync: true`, and pins
`mayInfluenceRollback: false`.

### 7.2 Live-Ops Calendar — `@maat/strategy/v2-fighting-game-live-ops-calendar`

This subsection defines the **V2 Fighting-Game Live-Ops Calendar Contract**,
served to the V2 program through the `@v2/maat-live-service-calendar` package
(depending on `@maat/strategy` at `workspace:*`). Maat strategy is authoritative
for the season calendar and Crown Points economy; the V2 wrapper marks
`standaloneV2LiveCalendarReplaced: true`, asserting that there is no separate V2
calendar schema, and exposes the engine read hooks `Rank.GetCrownPoints` and
`Esports.QualifyForFinals`.

`libs/maat/strategy/src/v2-fighting-game-live-ops-calendar.ts` exports
`MAAT_V2_LIVE_OPS_CALENDAR_SOURCE_OF_TRUTH_ID`, the Pro Circuit tier values
(`MAAT_V2_PRO_CIRCUIT_TIER_VALUES`), and the live-ops event kinds
(`MAAT_V2_LIVE_OPS_EVENT_KIND_VALUES`). Types include `MaatV2LiveOpsSeason`,
`MaatV2ProCircuitEvent`, `MaatV2AnniversaryEvent`, `MaatV2CharityEvent`,
`MaatV2BalanceRampWindow`, and `MaatV2CrownPointsRule`. The functions
`calculateMaatV2CrownPoints(input)` and
`buildMaatV2FightingGameLiveOpsCalendar(...)` compute Crown Points and assemble
the calendar. Maat owns seasons, ranked-season windows, Crown Points pool sizing
and tier scoring, the Pro Circuit calendar, and anniversary/charity/balance-ramp
windows. Rollback frames never call the calendar.

### 7.3 Balance Dashboard — `@maat/dashboard/v2-balance-dashboard`

This subsection defines the **V2 Balance Dashboard Contract**, exposed to the V2
program as `@v2/maat-balance-dashboard`. That service composes three Maat
libraries — `@maat/intelligence` (trend and anomaly detection),
`@maat/dashboard` (panel/manifest governance), and `@maat/reporting` (scorecards
and operational KPI charts) — each pinned at `workspace:*`; it deliberately does
**not** depend on `@maat/analytics`. The wrapper sets
`standaloneV2DashboardReplaced: true`, confirming there is no standalone V2
balance dashboard, and pins `mayInfluenceRollback: false`.

`libs/maat/dashboard/src/v2-balance-dashboard.ts` exports
`MAAT_V2_BALANCE_DASHBOARD_ID` (`maat.v2.balance-dashboard`) and
`buildMaatV2BalanceDashboardSurface(input)`. The contract declares the companion
`@maat/reporting` and `@maat/intelligence` package names by reference; it does
not import them. `MAAT_V2_BALANCE_DASHBOARD_EVENT_TOPICS` lists eight topics:
`v2.match.ended`, `v2.match.combat.move.used`, `v2.match.combat.damage.applied`,
`v2.match.combat.attack.blocked`, `v2.match.combat.frame-data.drift`,
`v2.match.balance.ab-result.published`, `v2.match.balance.ptb-signal.recorded`,
and `v2.match.performance.frame-budget-exceeded`. Panel kinds: `pick-rate`,
`win-rate`, `win-rate-on-block`, `frame-data-drift`, `hot-cold-heatmap`,
`ab-publication`, `release-gate`. The output is a dashboard/reporting artifact;
it is off rollback and must not influence deterministic combat simulation.

---

## 8. Knowledge Graph Schema (`@maat/knowledge`)

`libs/maat/knowledge/src/knowledge-graph-neo4j-schema.ts` defines an
**in-memory** graph schema (TypeScript records and Zod validation; no
`neo4j-driver` dependency). The schema is designed for a future Neo4j
projection: every node record carries a `neo4jLabel` field, and relationship
types are modelled to map directly onto Neo4j relationship labels.

The vocabulary is:

- `KG_NODE_TYPE_VALUES`: `ORGANIZATION`, `PERSON`, `PRODUCT`, `MARKET`,
  `REGULATION`, `TECHNOLOGY`, `LOCATION`, `EVENT`, `CONCEPT`.
- `KG_RELATIONSHIP_TYPE_VALUES`: `COMPETES_WITH`, `SUPPLIES_TO`, `REGULATED_BY`,
  `LOCATED_IN`, `INVENTED_BY`, `PART_OF`.
- `KnowledgeGraphNodeRecord` (`nodeId`, `nodeType`, `neo4jLabel`, `properties`,
  `createdAt`, `updatedAt`, `version`) and `KnowledgeGraphRelationshipRecord`
  model nodes and edges.

---

## 9. Frontend Applications

`apps/maat/` contains **11 application roots**: 5 implemented Next.js web apps,
1 implemented Hono service app, and 4 bootstrap scaffold service apps.

### 9.1 Implemented Next.js Apps (5)

Each app targets a distinct user persona: portfolio-level executives use the
dashboard app, operations teams use agent-console, analysts use intel, investors
use the investor portal, and strategists use the war room. Each runs on its own
Next.js port (e.g. `dashboard` on `3055`) and has `build`/`start`/`lint`/
`typecheck`/`test` Nx targets.

| App             | Package               | Routes (route groups)                                                                                                                                                                                   |
| --------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dashboard`     | `maat-dashboard-app`  | `(auth)/login`, `(dashboard)/portfolio`, `(dashboard)/company`, `(dashboard)/company/[companyId]`, `(dashboard)/alerts`, `(dashboard)/layout-builder`; API routes `/api/auth/login`, `/api/auth/logout` |
| `agent-console` | `@maat/agent-console` | `(console)` — `tasks`, `approvals`, `fleet`, `conversations`, `analytics`                                                                                                                               |
| `intel`         | `@maat/intel`         | `(intel)` — `landscape`, `news`, `pricing`, `regulatory`, `sizing`                                                                                                                                      |
| `investor`      | `@maat/investor`      | `(portal)` — `dashboard`, `portfolio`, `explore`, `simulate`, `onboard`                                                                                                                                 |
| `war-room`      | `@maat/war-room`      | `(war-room)` — `scenario`, `market-entry`, `strategy-canvas`, `synergies`, `capital`                                                                                                                    |

### 9.2 Implemented Service App (1)

`api-gateway` (`maat-api-gateway-app`) — the Hono gateway described in §5.

### 9.3 Scaffold Service Apps (4)

`agents` (`maat-agents-app`), `intelligence` (`maat-intelligence-app`),
`simulation` (`maat-simulation-app`), and `worker` (`maat-worker-app`) each
contain only a `bootstrap<name>()` function returning an `"<name> initialized"`
string. They are placeholders for future deployable services.

---

## 10. Technology Stack

The table below summarizes the technology choices for the domain. The most
important entry for new contributors is the last row: there is no persistence
layer wired in. All domain state lives in-memory. The Redis namespace and Neo4j
schema modules are forward-looking contracts, not active connections.

| Layer             | Technology                                                        |
| ----------------- | ----------------------------------------------------------------- |
| Language          | TypeScript (Node.js)                                              |
| Validation        | Zod                                                               |
| Gateway framework | Hono (`@hono/zod-validator`, `@hono/swagger-ui`)                  |
| Gateway auth      | `jsonwebtoken` (HS256)                                            |
| Frontend          | Next.js (App Router)                                              |
| LLM SDK           | `@anthropic-ai/sdk` (used only in `@maat/intelligence`)           |
| Build             | Nx with `@nx/js:tsc` (libs / gateway) and `next build` (web apps) |
| Testing           | Vitest (`@nx/vite:test`)                                          |
| Persistence       | None — all domain state is in-memory value objects                |

There is no PostgreSQL, Drizzle, Neo4j driver, or Redis client wired into the
domain. The "Redis namespace" and "Neo4j schema" modules produce keys and record
shapes for a future datastore integration but do not connect to one.

---

## 11. Validation Rules

The `@maat/core` Zod schemas enforce a set of domain invariants at construction
time. These rules apply to all entities and must be respected in any code that
creates or modifies Maat domain objects.

Entity-level rules:

- **Strict objects** — every entity schema uses `.strict()`, rejecting unknown
  keys.
- **ID patterns** — IDs must match their prefixed regex (§1.1).
- **Currency** — `currencyCode` must be a 3-letter ISO 4217 code; **country**
  codes must be 2-letter ISO codes.
- **Percent fields** — `0 ≤ value ≤ 100`; **probability/score fields** —
  `0 ≤ value ≤ 1`; **fiscal years** — integer `1900 ≤ year ≤ 9999`.
- **Ghana GH jurisdiction** — a `LegalEntityRegistration` with
  `jurisdictionCode === 'GH'` must use a Ghana company registration number.
- **Exactly one primary registration** — `LegalEntityStructure.registrations`
  must contain exactly one entry with `isPrimary === true`.
- **Board terms** — a board member's `termEndAt` cannot precede `appointedAt`.
- **Non-empty collections** — `Organization.operatingCompanies` and
  `LegalEntityStructure.registrations` require at least one entry.
- **Agent capabilities** — `createAgent` rejects capabilities not listed for the
  agent's class in `AGENT_CAPABILITY_MATRIX`.

Event validation rules (enforced by `event-bus.ts`): `eventId` must be a UUID,
`schemaVersion` must be the literal `1`, `occurredAt` must be ISO-8601 with
offset, confidence values are bounded `0–1`, `expectedImpactScore` is bounded
`−100…100`, and `StrategyDecisionEvent.approvedBy` requires at least one
approver.

---

## 12. Acceptance Criteria

A Maat change is acceptable when all of the following conditions hold:

1. New or changed `@maat/core` entities ship the full set — interface, `*Draft`,
   `*_VALUES` arrays for any new enum, Zod schema, `create*`,
   `create*WithDefaults`, `serialize*`/`deserialize*`, and `is*` — and are
   re-exported from `index.ts`.
2. Every schema is `.strict()` and every constructor throws on invalid input.
3. `npx tsc --noEmit` and `npx vitest run` pass in the affected library
   directory (Nx may be bypassed per repository policy).
4. New API gateway routes declare a `MaatPermission`, enforce
   `ensureOrganizationAccess` for org-scoped data, and are reflected in the
   generated OpenAPI spec.
5. New domain events extend `BaseMaatEventSchema`, are added to
   `MaatDomainEventTypeSchema` and `MAAT_EVENT_SCHEMAS`, and validate through
   `parseMaatDomainEvent`.
6. V2 contract changes preserve the rollback-exclusion and
   source-of-truth-ownership rules in §7.
