# Cybele — Systems Deep Dive

> The `apps/cybele/` area: five self-contained Hono application modules that
> make up the Cybele real-estate / built-environment domain — a
> construction-projects API, a property portfolio dashboard, a listings
> marketplace, a prefab factory manager, and an API gateway that fronts them.

## What this area is

Cybele is Oshun's real-estate and physical-space domain (the counterpart to
Freya's luxury-goods supply side, as noted on the Contracts area page). The
`apps/cybele/` directory holds its application tier: five Nx projects, each a
`projectType: "application"` under `scope:cybele`, each built as a `hono` HTTP
app. They are written for a Ghana market — money is denominated in Ghana Cedi
(`GHS`) throughout, listings carry `region`/`district`, and the gateway's
hand-written OpenAPI document advertises `https://api.cybele.gh/v1` with a
`engineering@cybele.gh` contact.

Each project follows an identical shape: `src/index.ts` constructs a
`new Hono()` app, registers `/health` and `/version` endpoints (every app
reports `version: '0.2.0'`), mounts a single feature router from
`src/routes/<name>.ts`, and `export default`s the app. The four domain apps
mount their router under `/api/v1/<name>`; the gateway mounts its router at
`/gateway`. The substance of each app lives in that one route file (527–893
lines apiece), where the domain types, Zod request schemas, in-memory stores,
and handlers all sit together.

These are genuinely-implemented apps, not scaffolds — they carry real
domain-specific logic (PMI earned-value formulas, sliding-window rate limiting,
faceted property search, NOI / gross-yield computation, prefab pricing with
option adders). Two honesty caveats apply across the area, though. First,
**persistence is in-process only**: every store is a module-level
`new Map<...>()` that starts empty and is populated through POST handlers, so
all state is per-process and ephemeral — there is no database, cache, or
contracts package wired in. Second, **there is no server bootstrap**: no
`serve()`, no `@hono/node-server`, no `main.ts`, and no Dockerfile is tracked.
The apps export Hono app objects that are exercised in-process (via
`app.request(...)`) rather than bound to a listening port here.

The five apps are also **self-contained**: across all of `apps/cybele/*/src` the
only imports are `hono`, `@hono/zod-validator`, and `zod` (plus `vitest` in the
test file). They do not import `@contracts/cybele` or any other workspace
library — each route file re-declares its own domain interfaces inline. So while
the area maps conceptually onto the Cybele wire contracts, that coupling is not
expressed as a code dependency today.

## How it fits the wider system

The intended topology is gateway-in-front: `@cybele/app-api`'s service registry
is seeded with exactly four built-in services — `cybele-projects` (`:3001`),
`cybele-portfolio` (`:3002`), `cybele-marketplace` (`:3003`), and
`cybele-factory` (`:3004`) — one per sibling app, and its OpenAPI document
catalogs the same four surfaces. The gateway models cross-cutting concerns
(token issuance, rate-limit policy, audit logging, health aggregation, webhook
fan-out) for that fleet. In practice the gateway only _registers and pings_
those services; it does not proxy traffic to them, and the four domain apps run
independently of it.

The shared verification seam is `apps/cybele/api/src/cybele-apps.test.ts` — a
single Vitest suite (69 `it` cases across 16 `describe` blocks) that imports all
four domain routers plus the gateway router and exercises them through isolated
Hono instances. Tellingly, only `@cybele/app-api` declares a `test` target in
its `project.json`; the other four projects declare `lint` only, because their
coverage lives inside that one cross-cutting suite.

## Entity reference

### @cybele/app-api

The Cybele **API gateway** (`apps/cybele/api`), whose logic is in
`src/routes/gateway.ts`. It owns the platform-level concerns for the domain
fleet: a service registry pre-seeded with the four sibling services, JWT token
issue/revoke/inspect, a sliding-window rate-limiter (`/rate-limits/check`
implements real per-minute/per-hour windowing and `isBlocked` accounting),
API-version listing, an in-memory audit log, per-service health aggregation and
ping, dynamic service registration, a hand-authored OpenAPI 3.1 document at
`/openapi.json`, and webhook subscription CRUD with delivery records and
history. Honest caveat on auth: token issuance is explicitly a **simulation** —
the handler base64-encodes a payload (`const fakeToken = Buffer.from(...)...`)
with a `// Simulate JWT (in production would use jsonwebtoken)` comment, so
tokens are unsigned, not verifiable JWTs. This project is also the home of the
area's shared test suite (`src/cybele-apps.test.ts`) and the only one with a
`test` target.

### @cybele/app-factory

The **prefab / modular factory manager** (`apps/cybele/factory`,
`src/routes/factory.ts`). It models a production line for building modules:
production orders, station-based scheduling, QC station results with computed
pass-rate (`computeQCPassRate`), material inventory with reorder-level /
low-stock detection, dispatch and logistics records, and a KPI dashboard
(`/kpis`) that aggregates QA pass rate, factory-utilisation percentage,
low-stock counts, and a `summarizeDefects` top-defects ranking. Its most
domain-specific endpoint is `/modules/:configId/quote`, which prices a module
configuration from a `basePriceGHS` plus per-option `priceAdderGHS` adders
against `selectedOptions`, returns unit and total price, lead-time weeks, and a
`canFulfil` check against `minOrderQty`. State is `Map`-backed and starts empty.
Declares a `lint` target only.

### @cybele/app-marketplace

The **property listings marketplace** (`apps/cybele/marketplace`,
`src/routes/marketplace.ts`) — the largest of the four domain route files'
search surface. It provides listing CRUD plus a real faceted `/search` that
filters on region, district, property/transaction type, price band, bedrooms,
area, comma-separated required features, and free-text `q` (title/description/
address), then sorts (`price_asc`/`price_desc`/`most_viewed`/recency),
paginates, and returns `countBy` facet histograms. Beyond search it has
popularity-ranked recommendations (`inquiryCount + viewCount`), agent
management, inquiry/lead capture, viewing appointments, listing comparison,
market analytics, per-user favorites, and price alerts. All data lives in
in-memory `Map`s seeded only through POST handlers. Declares a `lint` target
only.

### @cybele/app-portfolio

The **property portfolio / asset-management dashboard**
(`apps/cybele/portfolio`, `src/routes/portfolio.ts`). It manages owned
properties and their leases, rent collection with arrears tracking, and
maintenance tickets, and computes real property financials in
`/:propertyId/financials`: gross rental income, maintenance cost, a property tax
of 0.5% of current valuation, net operating income (NOI), gross yield, and
unrealised gain over acquisition cost. It also handles valuations (recomputing
`yieldPct` and updating the property's `currentValuationGHS`), a forward-looking
lease-expiry calendar (`/lease-calendar`), and a `/map` endpoint that emits a
GeoJSON `FeatureCollection` of property points with occupancy percentages.
`Map`-backed, empty at start. Declares a `lint` target only.

### @cybele/app-projects

The **construction project-management** app (`apps/cybele/projects`,
`src/routes/projects.ts`) — the largest route file (893 lines) and the most
algorithmically rich. It covers projects and progress updates, a Gantt schedule,
daily reports, RFIs (with responses), change orders (with approval), QC
inspections, and safety incidents (with lost-time/fatality tallies). Its
standout endpoint is `/:projectId/earned-value`, a full PMI earned-value-
management calculator: from PV/EV/AC/BAC it derives SPI, CPI, schedule and cost
variance, ETC, EAC, VAC, and TCPI, then assigns a green/amber/red health band by
threshold. Documents support revision control (a new upload supersedes the prior
`current` revision of the same title), and `/:projectId/notifications/stream` is
a real Server-Sent-Events response (`ReadableStream` emitting a `connected`
event then a 30-second `heartbeat`) — honestly a heartbeat stream rather than a
wired event subscription, as its own comment notes. State is in-memory and
ephemeral. Declares a `lint` target only.
