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 defaults 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 catalog (5)#
The 5 tracked Nx projects in cybele, each a code-linked entity node — package, type, source path, declared targets, and its internal dependency graph (depends-on / used-by, resolved from the package manifests, §6/§8), read from the project graph. Grouped by architectural layer; walk the dependency links to travel the system. 5 of these carry an authored deep-dive (what / why / how it fits); the rest are generated scaffolds awaiting one.
unclassified (5)#
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.
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.
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 Maps seeded only through POST handlers. Declares a lint target
only.
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.
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.