# Cybele Domain — Architecture

> Built-environment intelligence for Ghana's property lifecycle — land, design,
> construction, operations, finance, and PropTech — implemented as a
> TypeScript/Nx workspace under `libs/cybele/*`, `libs/contracts/cybele`, and
> `apps/cybele/*`. (Scope tag `scope:cybele`; API port `4027`; database schema
> `cybele`; build-out tracked as TODO Phase 58.)

---

## What Cybele Is, and Why It Exists

Cybele is the Oshun bounded context for the built environment. It models the
full lifecycle of property as a single connected system: a parcel of land is
analysed for suitability, designed against a building code, delivered through a
construction project, operated as income-producing real estate, financed with
mortgages and REIT capital, and listed on a PropTech marketplace. The same
identity threads through all of it — a `PlotId` analysed in site-analysis is the
plot a `Building` is constructed on, whose `Unit`s carry the `Lease`s that a
portfolio values and that a marketplace listing advertises.

The real-world problem is fragmentation. In Ghana, land registries, construction
delivery, tenant leases, and investment portfolios live in disconnected silos,
so integrated suitability scoring, cost tracking, and portfolio valuation are
impossible to do across the asset's life. Cybele's answer is a shared type
system (`@cybele/core`) plus geospatial-aware persistence (`@cybele/db`) plus
stable cross-domain contracts (`@contracts/cybele`), so that every score,
milestone, lease, and projection references one property and plot identity.

Cybele is a **TypeScript domain**, not a Rust engine — it sits in the
service/application tier of the stack, not the runtime kernel tier (contrast
Neith). It depends on workspace infrastructure (Hono, Drizzle, kafkajs, Zod,
`node:crypto`) and is consumed by sibling business domains — Brigid (factory
automation inside facilities Cybele tracks as assets), Asase (agricultural-site
infrastructure), Freya (real-estate envelopes for luxury retail/manufacturing),
and Maat (organization-wide capital and governance rollups) — through the
contract package and the integration bridges, never through Cybele internals.

## Workspace Shape

The domain is nineteen library packages, one contracts package, and five
deployable application services:

```text
libs/cybele/
  core/  common/  db/  api/                      ← foundation
  site-analysis/  design/  construction/         ← deliver
  property/  industrial-parks/  hospitality/     ← operate
  infrastructure/
  prefab/  materials/                            ← manufacture
  finance/  financials/  market-intel/           ← finance & analyse
  proptech/                                       ← market
  integration/  testing/                          ← cross-cutting

libs/contracts/cybele/                            ← stable inter-domain schemas + events
apps/cybele/  api/  portfolio/  projects/  marketplace/  factory/
```

The `libs/cybele/api` directory is the largest single package (34 tracked
files); `site-analysis` (26) and `design` (22) follow. The five apps are thin
Hono surfaces — each is a `src/index.ts` plus a single `src/routes/<name>.ts`
file — that compose library logic into one operational workflow apiece.

## Architectural Layers

The packages form five layers that build strictly upward.

**1. Core type system — `@cybele/core`.** The canonical vocabulary. Source
`libs/cybele/core/src/types.ts` (re-exported from `index.ts` alongside
`validators`, `guards`, `serializers`) defines twelve **branded ID types**
(`PropertyId`, `PlotId`, `BuildingId`, `UnitId`, `TitleId`, `ProjectId`,
`TenantId`, `LeaseId`, `ContractorId`, `MaterialId`, `InvestorId`, `FundId` —
each `string & { readonly __brand }` with a `make…` factory), the string-valued
state-machine enums (`PropertyStatus`, `ConstructionStatus`, `LeaseType`,
`ProjectPhaseType`, `ZoningClass`, `LandTenure`, the 17-value `GhanaRegion`),
Ghana-specific spatial/address types (`GhanaAddress` with the Ghana Post GPS
regex `^[A-Z]{2}-\d{3}-\d{4}$`, `GeoLocation`/`GeoPolygon` carrying an explicit
`srid`), and every domain aggregate (`Plot`, `Property` + four discriminated
subtypes, `ConstructionProject`/`ProjectPhase`/`Milestone`, `Lease`, finance
primitives, materials, specialized facilities, and the Ghana Lands Commission
API interop types). `validators.ts` wraps each enum in a Zod `z.nativeEnum(...)`
schema; `guards.ts` provides duck-typed (field-presence, not `instanceof`) type
guards that survive serialization boundaries. Every other package imports from
here; nothing here imports from any other Cybele package.

**2. Shared utilities — `@cybele/common`.** Math, date, geo, ID helpers, plus a
substantial `carbon-esg.ts` whole-life carbon calculator (EN 15978 lifecycle
stages `A1–A3 … D`, Ghana-sourced material emission factors, GRESB/TCFD-aligned
reporting). Pure functions, no I/O.

**3. Data — `@cybele/db`.** The Drizzle/PostgreSQL schema. `src/schema.ts`
declares 44 `pgTable`s and 19 `pgEnum`s, plus a custom Postgis column type
`cybelePostgisPolygon` rendering `geometry(Polygon,4326)` for plot boundaries.
Nine numbered migrations (`drizzle/0000…0008`) cover the initial schema,
TimescaleDB IoT hypertables, partitioning, the PostGIS boundary, the spatial
building hierarchy, construction WBS, rent schedules, prefab/orders/assembly,
and competitor developments. `connection.ts` defines per-service-role PgBouncer
`PoolProfile`s (`ecommerce`, `construction`, `property`, `analytics`, `iot`,
`hospitality`, `finance`, `admin`) and a `generatePgBouncerIni(...)` config
generator; `redis-config.ts` defines the `cybele:` key builders, per-entity
TTLs, and `cybele:events:*` pub/sub channels. These are configuration and
helpers — they describe how infrastructure is wired, they do not themselves run
PgBouncer or Redis.

**4. Capability libraries.** Each real-estate or construction subdomain owns its
algorithms on top of core + db. These are the bulk of the domain logic and are
genuinely domain-specific, not CRUD:

- `site-analysis` — multi-criteria decision analysis (MCDA) site scoring with
  sensitivity analysis and RICS highest-and-best-use (`selection.ts`); DEM
  topography via Horn's slope/aspect method (`dem-provider.ts`); demographics,
  infrastructure access, flood/seismic/air-quality risk; land-registry checks;
  ML/NLP-flavoured `ai-selection.ts`.
- `design` — structural, MEP, sustainability, BIM (`bim.ts`), drawings, and
  `generative.ts` (parametric massing/unit-mix optimisation under setback/plot-
  ratio constraints) plus a BIM-to-`digital-twin.ts` pipeline.
- `construction` — CPM forward/backward pass and PERT (`scheduling.ts`,
  PMI/PMBOK), cost/earned-value, procurement, quality, progress, and drone/
  LiDAR safety inference (`drone-inference.ts`, `ar-lidar.ts`).
- `property` — leases, tenants, maintenance, valuation, analytics,
  smart-building `iot.ts`.
- `prefab` + `materials` — modular module catalogs, factory production,
  affordability, logistics; concrete/steel/paint/tile/furniture product physics
  with Ghana Standards Authority certification fields.
- `finance` + `financials` — Ghana mortgage products and amortisation
  (`mortgage.ts`, with real 2024–2026 GHB/Republic/diaspora rate data), REIT
  NAV, development pro formas, crowdfunding, cross-business-unit cost/synergy
  models.
- `market-intel` — price/rent indices, comparables, supply pipeline, economic
  indicators.
- `industrial-parks`, `hospitality`, `infrastructure` — the distinct property
  classes (utility-capacity-bound parks, hotel asset/RevPAR operations, linear
  civil assets with governmental clients).
- `proptech` — listings, CRM, digital experience, transactions, and a
  cryptographic `blockchain-title.ts` registry (Ethereum-style `Wallet`,
  keccak256, EIP-191, secp256k1 ECDSA over `node:crypto`).

**5. Contracts, API gateway, and applications.** `@contracts/cybele` is the
stable boundary (schemas + events, below). `@cybele/api` is the runtime gateway
(Hono + auth + Kafka + GraphQL + gRPC + WebSocket + the construction ledger).
The five `apps/cybele/*` services compose all of the above into deployable
surfaces.

```mermaid
flowchart TD
  subgraph apps["apps/cybele/* (Hono services)"]
    A_API[api / gateway]:::app
    A_PORT[portfolio]:::app
    A_PROJ[projects]:::app
    A_MKT[marketplace]:::app
    A_FAC[factory]:::app
  end

  subgraph gw["@cybele/api gateway runtime"]
    REST["REST routers: properties · construction · leases · materials"]
    GQL[GraphQL schema]
    GRPC[5 gRPC service defs]
    WS[WebSocket channels]
    LEDGER[construction ledger]
    KAFKA[Kafka publisher / consumer]
  end

  subgraph cap["capability libraries (domain algorithms)"]
    SITE[site-analysis] & DES[design] & CON[construction]
    PROP[property] & FIN[finance] & MAT[materials] & PT[proptech]
  end

  CORE["@cybele/core (types · validators · guards)"]:::core
  DB["@cybele/db (Drizzle + PostGIS · PgBouncer · Redis cfg)"]:::core
  CONTRACTS["@contracts/cybele (Zod schemas · 31 CloudEvents)"]:::core
  INTEG["@cybele/integration bridges"]

  apps --> gw
  gw --> cap
  cap --> CORE
  cap --> DB
  gw --> CORE
  KAFKA -->|CloudEvents 1.0| CONTRACTS
  INTEG -->|approved facts| EXT["Brigid · Saraswati · Asase · Freya · Maat"]
  cap --> INTEG

  classDef app fill:#1f6feb22,stroke:#1f6feb;
  classDef core fill:#2da44e22,stroke:#2da44e;
```

## The API Gateway

`createGateway(config)` in `libs/cybele/api/src/gateway.ts` builds a single Hono
app rooted at `/api/v1`. The middleware pipeline (in order) is secure-headers →
CORS → request-id → structured JSON logging → OpenTelemetry tracing → Prometheus
metrics → request timeout → per-IP sliding-window rate limit (Redis-backed, 200
req/min default) → JWT auth. Four paths bypass auth: `/health`, `/metrics`,
`/listings`, and `/market-intel/prices` (public reads).

Honest scope note: the gateway **mounts four REST route groups** —
`propertiesRouter`, `constructionRouter`, `leasesRouter`, `materialsRouter` —
which are the four primary-aggregate REST families. The
`CYBELE_SERVICE_REGISTRY` constant enumerates eight logical service slots
(adding `tenant`, `listings`, `finance`, `market-intel`); those four extra slots
are a dispatch/health-aggregation table and are served through the GraphQL
endpoint and the application services rather than by a mounted REST router in
`createGateway`. The `propertiesRouter` writes through a `PropertyRepository`
interface with two implementations — `DrizzlePropertyRepository` (real Postgres)
and `InMemoryPropertyRepository` (default, swappable via
`setPropertyRepository`) — so the route logic is testable without a database.

The gateway also exposes GraphQL (`graphql/schema.ts` — `typeDefs` +
`resolvers`), five protobuf service definitions (`grpc/definitions.ts`:
property, construction, lease, finance, notification), and a WebSocket server
(`websocket/server.ts`) whose `canSubscribeToChannel(channel, role)` gates
`project:<id>` and agent channels by role. The deployable `apps/cybele/api` is a
thin wrapper: it serves `/health` and `/version` (`0.2.0`) and mounts a
`/gateway` router that adds gateway-management endpoints (routing table, client
tokens, per-client rate limits, audit log, OpenAPI doc, webhook subscriptions).

## Domain Events

`@contracts/cybele/src/events.ts` is the event canon. It defines the CloudEvents
1.0 envelope (`CybeleEventSchema`), a registry `CYBELE_EVENT_TYPES` of **31
event types** across six aggregates (property 5, construction 7, lease 6,
finance 5, prefab 4, market 4), `buildCybeleEvent(...)` /
`validateCybeleEvent(...)` factory+validator, and `resolveKafkaTopic(...)` that
maps an event type to one of six **logical** topics (`cybele.properties`,
`cybele.construction`, `cybele.leases`, `cybele.finance`, `cybele.prefab`,
`cybele.market`) by prefix.

The runtime in `libs/cybele/api/src/kafka.ts` uses a **second, physical** topic
registry — `cybele-property-events`, `cybele-construction-events`,
`cybele-lease-events`, `cybele-finance-events`, `cybele-iot-events`,
`cybele-notifications`, `cybele-audit-events` — with recommended partition
counts (IoT highest at 24). `CybeleEventPublisher`/`CybeleEventConsumer` wrap a
real `kafkajs` client: GZIP compression, CloudEvents headers (`ce-*`), partition
key = `partitionKey ?? subject` (so all events for one property/tenant/project
stay ordered), `retries`, a dead-letter path (`createDeadLetterRecord` → DLQ
topic or handler), and consumer errors are swallowed after DLQ to avoid group
rebalance. A broker is required at runtime; `createDefaultPublisher()` reads
`KAFKA_BROKERS`. The two registries are an intentional split: contracts expose
stable logical names to consumers, the runtime owns physical topic/partition
layout.

## The Construction Ledger

`libs/cybele/api/src/routes/construction-ledger.ts` is the most
security-sensitive component. Schedule/budget mutations append a hash-chained,
signed event to a per-project chain: `budget_committed` (POST /construction),
`progress_attested` (PUT /construction/:id), `payment_settled` (POST
…/progress-claim), `milestone_achieved` (PUT …/phases/:phaseId at 100%). Each
event carries a sha-256 `transactionHash`, an incrementing `blockNumber`, a
`prevHash` linking it to the previous event, and an ECDSA **secp256k1**
signature (validator key deterministically derived from a signing secret via
`createECDH('secp256k1')` + JWK import). `verifyChain(projectId)` re-walks the
chain and returns the offending index + reason (`prev_hash_mismatch` /
`tx_hash_mismatch` / `invalid_signature`) on tamper. `deriveEvmInputs(...)`
aggregates confirmed events into the four Earned-Value scalars (BAC, %-complete,
AC, PV) the EVM endpoint needs, with a linear S-curve planned-value
approximation (documented as the Ghana FIDIC default absent a cost-loaded
schedule).

This is a real cryptographic ledger, but the default backend is
`InMemoryConstructionLedger` — the chain lives in process memory and is what the
route unit tests run against. The `ConstructionLedger` interface plus
`setConstructionLedger(...)` is a **fail-loud seam**: a production deployment
injects an EVM/JSON-RPC client wrapping the real construction smart-contract
suite. The crypto is identical to `proptech/blockchain-title.ts`; the two
intentionally duplicate ~20 lines of signing helpers rather than create a
`@cybele/api → @cybele/proptech` dependency cycle.

## Provider Seams (Implemented vs Deployable)

Several capability libraries that touch heavy external models follow one
pattern: a **`Fixture…` backend** (deterministic, pure-TS, in-memory) plus a
**production backend** that dynamic-imports a native binding.
`construction/drone-inference.ts` has `FixtureDroneInferenceBackend` and
`OnnxDroneInferenceBackend` (YOLOv8/RT-DETR ONNX decode + NMS via
`onnxruntime-node`); `site-analysis/dem-provider.ts` has `FixtureDemProvider`
and `SrtmDemProvider` (SRTM GeoTIFF/`.hgt` via `gdal-async`, Horn slope/aspect);
the EPA/borehole/NDVI providers follow suit. The fixture backends are fully
implemented and test-covered; the native backends are written but require their
native dependency and model/tile files to be present at deploy time. This is the
honest boundary — the algorithms are real, the heavyweight runtime is pluggable
and not bundled.

## Cross-Domain Boundaries

`@cybele/integration` holds the bridge contracts to the five sibling domains —
`brigid-bridges.ts`, `saraswati-bridges.ts`, `asase-bridges.ts`,
`freya-bridges.ts`, `maat-bridges.ts`. Each defines the typed exchange surface
(e.g. `BrigidFactoryAutomationBridge` with `PrefabProductionOrder`/
`ProductionStatus` and a `BridgeHealthCheck`) and currently keeps state in
in-memory `Map`s; the actual network transport to the `*_API_URL` endpoints is
configuration-level. The directional rules are fixed: Cybele **owns** land,
property, construction, BIM/design, prefab, real-estate finance, and property
operations; Brigid owns plant control inside facilities Cybele tracks as assets;
Hestia/Annapurna own culinary operations inside Cybele's restaurant buildings;
Freya owns retail/manufacturing business semantics inside Cybele's envelopes;
Maat reads Cybele's financial/risk outputs for governance. A **Gaia
weather/climate integration** (Phase 175 — asset-hardening alerts, site weather
risk, flood/heat analysis) is referenced by the specs and modelled in the
`WeatherCondition` type, but there is no `gaia-bridges.ts` in `integration/src`
today; treat it as planned rather than wired.

## Invariants, Failure Modes, Extension Points

**Invariants.** Coordinates always carry their `srid` (default `4326`) and are
never computed against without a CRS. Monetary values are GHS unless a field
names its own currency. Construction schedule/budget changes are auditable via
the append-only hash-chained ledger; corrections never mutate prior events.
Financial projections retain their input assumptions for reproducibility. Lease
and tenant records enforce role-based access and immutable signoff.
`@cybele/core` has no intra-domain upstream dependencies.

**Failure modes.** The gateway swallows handler errors into a `CYBELE_500`
envelope (message hidden in production) and rate-limits at 200 req/min/IP. Kafka
consumer failures are DLQ'd and swallowed to prevent rebalance storms. The
ledger fails loud on tamper (`verifyChain`). Provider native backends throw if
their binding/model is absent rather than fabricating detections — the fixture
backend is the safe default for tests. PgBouncer transaction-mode pools disable
prepared statements (`prepare=false`); migrations must use the direct
(`buildDirectUrl`) non-pooled path for DDL.

**Extension points.** Add an aggregate by extending `@cybele/core` types +
validators + guards, a `pgTable` + migration in `@cybele/db`, a capability
library, an event in `CYBELE_EVENT_TYPES`, and (if externally visible) a schema
in `@contracts/cybele`. Swap a runtime backend via the
repository/ledger/provider seams (`setPropertyRepository`,
`setConstructionLedger`, a `Fixture…` → production provider). Add a cross-domain
link with a new `*-bridges.ts` file.

## Implementation Status

The domain is an **implemented TypeScript workspace** with substantial,
genuinely domain-specific logic — MCDA site scoring, CPM/PERT scheduling, Horn
DEM analysis, Ghana mortgage/REIT/pro-forma finance, EN 15978 carbon, secp256k1
construction and title ledgers, and a real kafkajs/Hono/Drizzle/PostGIS stack.
What is **seam-level / not live by default**: the construction ledger and
property repository ship in-memory defaults behind production-injectable
interfaces; the ONNX/SRTM/EPA provider native backends require their bindings
and model files; the integration bridges define contracts with in-memory state
rather than live HTTP clients; and the Gaia (Phase 175) integration is
spec-referenced, not yet a bridge. Phase 58 (`TODOS/phase-58.md`) is the active
build-out phase; the `db` and `api` sources carry inline `58.1.2.x` / `58.1.3.x`
subtask anchors marking that work. These seams are honest fail-loud boundaries,
not stubs: they compute and refuse rather than fabricate.
