Luxury Goods and Fashion Intelligence operating system (
libs/freya/*,apps/freya/*,libs/contracts/freya; TODO Phase 57). An implemented in-process TypeScript domain: deterministic business engines today, with a persistence and transport surface defined as code but not yet wired to live infrastructure.
Freya is the bounded context for luxury-goods businesses operating in African fashion, beauty, textiles, jewelry, and accessories. It models the operating system a brand or conglomerate needs to create products, source materials, manufacture at scale, sell through multiple channels, and analyze financial performance — from a designer's first concept board through to a customer's purchase and the brand's per-business-unit P&L. It is intentionally supply-side: Freya runs the business of making and selling luxury goods. It does not make per-consumer styling decisions, and it does not own physical real estate; those concerns belong to Aglaea and Cybele respectively.
Freya sits in the application/domain tier of the Oshun monorepo, not the engine tier. Unlike Neith (a sovereign Rust runtime kernel that everything builds on), Freya is a TypeScript workspace that builds on shared platform contracts and publishes domain state outward. Its principal downstream consumer is Aglaea, the consumer-styling domain, which reads Freya's products, sizing, inventory, campaigns, and provenance to drive recommendation experiences. The dependency arrow points one way: no Freya library imports from Aglaea.
What "Implemented" Means Here#
The single most important thing to understand before reading further: every
Freya capability library and application is a deterministic engine that operates
over in-process Map-backed stores. The domain logic — state machines,
costing, AQL inspection, relevance scoring, formulation validation — is real,
specific, and fully implemented. The infrastructure around it (PostgreSQL,
Redis, MinIO, Kafka, gRPC) is defined as typed schema and configuration code but
is not actually connected. Concretely:
apps/freya/api/src/order-routes.tskeeps orders inconst orders = new Map<string, OrderResponse>()and exposesresetOrderStore()for test isolation — there is no database call in the request path.- No capability library or application imports
@freya/db. A repo-wide grep shows@freya/dbis referenced only by its own tests. The 24-table Drizzle schema is a parallel, currently-unconsumed persistence definition, not the backing store for the running code. apps/freya/api/src/event-bus.tsdefines the CloudEvents envelope, the event taxonomy, the Kafka topic-routing map, andbuildFreyaEventBusConfig(), but instantiates no Kafka producer.apps/freya/api/src/grpc-services.tssays so in its own header comment: "Defines Protobuf-style service contracts as TypeScript interfaces. Actual .proto files are generated from these definitions." It shipsgenerateProtoStub(), not a running gRPC server.
This is an honest, fail-loud architecture, not a faked one: the engines compute real results from real inputs; the persistence/transport layer is a complete contract waiting for a wiring phase. The sections below label each layer's status explicitly.
Workspace Shape#
Freya occupies three areas of the monorepo: twenty-six libraries under
libs/freya/* (two foundation, twenty-four capability), a shared contracts
package at libs/contracts/freya, and five applications under apps/freya/*.
libs/freya/
core/ db/ # foundation (everyone depends on core)
fashion/ textiles/ beauty/ # multi-module capability libraries
brand/ ecommerce/ manufacturing/
retail/ academy/ jewelry/
market-intel/ financials/ connectors/ sota/
watches/ perfumes/ bridal-events/ # single-module facade libraries
hair-care/ home-decor/ footwear/
eyewear/ luggage-leather/ personal-care/
cleaning-products/ textile-finishing/
libs/contracts/freya/ # @freya/contracts — shared Zod schemas
apps/freya/
api/ studio/ shop/ retail/ manufacturing/
Architectural Layers#
Foundation: @freya/core (implemented)#
@freya/core (libs/freya/core/src/index.ts) is the type-and-algorithm
foundation every other Freya package imports — dozens of source files across the
workspace depend on it. It is in-process TypeScript types and pure functions,
not database rows. Its modules are product-types.ts, material-types.ts,
supply-chain-types.ts, brand-customer-types.ts, beauty-types.ts,
business-units.ts, and utilities.ts.
The foundation is where Freya's domain correctness actually lives, and the algorithms are specific rather than generic CRUD:
- Branded identity. Six template-literal ID types (
FreyaProductId=FPRD-…,FreyaOrderId=FORD-…, etc.) make the compiler reject aFreyaOrderIdwhere aFreyaProductIdis expected. Generators build IDs asPREFIX{hexTimestamp}-{6-hex-sequence}-{4-hex-random}with a monotonic sequence wrapping at0x1000000. - Quality / supply chain (
supply-chain-types.ts):computeAQLSampleSizereturns standard sample sizes per lot band;evaluateQualityResultreturnsfailon any critical defect,conditional_passwhen minors exceed twice the AQL threshold,passotherwise;computeSupplierScoreis the weighted aggregateperformance×0.4 + onTime×0.3 + qualityPassRate×0.3. - Pricing (
product-types.ts):getPriceForMarketresolves a tier, prefers the requested currency, and falls back via a fixed market→currency map. - Beauty chemistry (
beauty-types.ts):validateINCISumenforces ingredient percentages summing to 100% ±0.5;computeFragranceAccordStrengthweights base notes ×1.5, heart ×1.2, top ×1.0. - Utilities (
utilities.ts): real EAN-13/UPC-A check-digit algorithms, fixedEXCHANGE_RATES_TO_GHS, textile unit conversions, a Ghana Kente/Ankara Pantone palette with nearest-color matching, and theFreyaErrorhierarchy (FreyaValidationError,FreyaNotFoundError,FreyaBusinessRuleError) used for fail-loud guards across every library.
The Business-Unit Catalog and Its Facade Libraries#
business-units.ts exports FREYA_BUSINESS_UNIT_CATALOG, a frozen array of
nineteen FreyaBusinessUnitProfile records (code, packageName,
displayName, summary, primaryCategories, operatingCapabilities,
productionModels, commercialChannels, keyMetrics).
assertCompleteFreyaBusinessUnitCatalog enforces exactly nineteen entries with
unique codes and packages.
Honest correction to features.md: eleven of the capability libraries —
watches, perfumes, bridal-events, hair-care, home-decor, footwear,
eyewear, luggage-leather, personal-care, cleaning-products, and
textile-finishing — are not domain-rich engines. Each is an 18-to-20-line
facade that reads its profile from the catalog.
libs/freya/watches/src/index.ts in its entirety exposes
getWatchesBusinessUnitProfile(), hasWatchesOperatingCapability(capability),
and listWatchesKeyMetrics() over getFreyaBusinessUnitProfile('watches').
These are honest, useful registry accessors — they fabricate nothing — but the
operational logic for those units lives in the multi-module libraries:
fragrance and haircare in @freya/beauty (fragrance-development.ts,
haircare-development.ts); personal-care and detergent in @freya/beauty
(personal-care-detergent.ts); finishing in @freya/textiles
(finishing-operations.ts); and assembled goods (footwear, eyewear, leather) in
@freya/manufacturing (assembly-manufacturing.ts).
Capability Libraries (multi-module, implemented)#
Thirteen libraries carry the bulk of the domain logic, each a set of focused
modules over in-memory state. Every one depends only on @freya/core. A few
representative, code-grounded examples:
@freya/fashion(design-pipeline.ts,collection-planner.ts,size-grading.ts,production-scheduler.ts,trend-forecasting.ts,african-print-library.ts).DesignPipelineis a guarded finite state machine overconcept → mood_board → sketch → pattern → prototype → sample → production(withdiscontinuedreachable from any stage and anALLOWED_TRANSITIONStable that throws on illegal moves).DesignCostEstimatorcomputes garment cost from Standard Minute Value:laborCost = (SMV/60) × rate,overhead = labor × 0.30.PrototypingTrackercaps revision cycles at five.@freya/ecommerce(catalog-inventory.ts,diaspora-commerce.ts,social-personalization.ts).InventoryService.computeStockLevelreturnsmax(0, warehouseStock − activeReservations), treating non-committed, non-expired reservations as held;reserveStock/commitReservation/releaseReservationimplement a TTL soft-lock with multi-warehouse draw-down (Accra first).OrderManagementService.transitionStatusenforces a fourteen-state DTC machine.SearchEngine.computeRelevanceScoreis the documented blendtext×0.4 + popularity×0.3 + recency×0.2 + margin×0.1.@freya/financials(unit-economics.ts,financial-planning.ts,startup-investment.ts).GarmentUnitEconomicscarries named constants (TRIMS_PCT_OF_FABRIC = 0.08,OVERHEAD_PCT_OF_CMT = 0.30,WHOLESALE_MULTIPLIER = 2.2,RETAIL_MULTIPLIER = 2.4) and computes a COGS breakdown plus wholesale/retail ladders.- Plus
@freya/textiles,@freya/beauty,@freya/manufacturing,@freya/retail,@freya/brand,@freya/jewelry,@freya/academy, and@freya/market-intel, each following the same pattern: domain-specific classes and pure functions overMapstores, guarded byFreyaBusinessRuleError/FreyaValidationError.
@freya/connectors (implemented as brief-builders; not wired)#
@freya/connectors (index.ts re-exporting asase-, brigid-, cybele-,
saraswati-, maat-connectors.ts) is the intended single point of
cross-domain integration. As built, it computes hand-off artifacts in-process
rather than calling the other domains. For example
cybele-connectors.ts::FactoryConstruction.buildFactorySpec(productionType, capacity, workforce)
derives floor area (workforce×8 + capacity×0.5 m²), power
(workforce×2 + capacity×0.1 kVA), clean-room class (ISO 8 for cosmetics), and
compliance standards (adding ISO 22716 GMP for cosmetics, OEKO-TEX/GOTS for
textiles); FlagshipStoreDesign.computeStoreFloorArea sizes a flagship from
peak traffic and dwell time. These are real luxury-retail/industrial heuristics
that produce a request payload — but there is no import of, or network call
to, Cybele/Brigid/Asase/Saraswati/Maat. The connector is the correct seam;
live dispatch across it is roadmap.
@freya/sota (implemented enrichment seam)#
@freya/sota (ai-design.ts, ai-analytics.ts, computer-vision.ts,
nlp.ts, blockchain-auth.ts, virtual-tryon.ts) houses the opt-in
state-of-the-art enrichment layer. It is deliberately separated from the
deterministic capability libraries so business logic stays testable and
predictable even when an AI enrichment is unavailable or returns an unexpected
result.
@freya/db (defined-as-code; unconsumed)#
@freya/db (schema.ts, connection.ts, redis-config.ts, s3-config.ts,
observability.ts, seed.ts) is a complete persistence specification:
twenty-four freya_* Drizzle tables (grep -c pgTable = 24) with UUID PKs,
jsonb columns, and four vector(1536) pgvector embedding columns;
buildFreyaDbPoolConfig sizing pools per service role; FreyaRedisKeys/
FreyaRedisTTL/FreyaPubSubChannels; three MinIO/S3 bucket policies; and an
OpenTelemetry/Prometheus/circuit-breaker config. It is real, careful schema and
config — but as noted above, nothing in the running domain imports it. The DB
integration test (integration.spec.ts) gates its live-Postgres assertions
behind skipIf(process.env.FREYA_DATABASE_URL === undefined) and otherwise
exercises pure logic (S3 key construction, INCI validation, Redis channel
naming), so the suite passes without infrastructure.
@freya/contracts (implemented)#
libs/contracts/freya/src/index.ts re-exports four Zod schema modules
(product-schemas, order-schemas, customer-schemas,
manufacturing-schemas) that form the stable, validated API boundary. These are
consumed by the API app's handlers and are the schemas any other Oshun domain
would use to call Freya.
Applications (implemented over in-memory state)#
Five apps compose the libraries into workflows. They depend on capability
libraries but never on each other. apps/freya/api is the surface
(schemas.ts, middleware.ts, the *-routes.ts handlers, graphql-schema.ts,
websocket.ts, grpc-services.ts, event-bus.ts, openapi-spec.ts).
studio, shop, retail, and manufacturing are the design, DTC, store-ops,
and factory-floor apps. The API enforces real RBAC in middleware.ts
(ROLE_PERMISSIONS for customer/associate/manager/admin, :own
ownership checks, three rate-limit buckets) and publishes a genuine OpenAPI
3.1.0 document (FREYA_OPENAPI_SPEC in openapi-spec.ts, with components,
paths, and $ref-linked schemas).
Component and Data Flow#
The intended operational cycle, much of which is implemented in-process today:
- Creation. Designers/operators create product, material, collection, and manufacturing records (studio + API). Objects get branded IDs and enter the product/design lifecycle state machines.
- Enrichment.
brand,market-intel, andsotaattach trend, campaign, demand, and pricing signals. (sotais opt-in; the deterministic core stands alone without it.) - Selling.
ecommerceandretailpublish onlyActiveproducts and run carts, checkout, payments, and omnichannel inventory against a shared stock model. - Manufacturing and quality.
manufacturingconsumes orders and closes the production loop with status updates and AQL-gated inspection; a failed inspection holds the order until rework re-passes. - Planning.
financialsandmarket-intelaggregate into per-unit P&L, brand health, and supplier-risk views that feed the next design cycle.
Three Order State Machines (a deliberate, real distinction)#
Freya models orders at three different surfaces, each with its own lifecycle — this is intentional, and a frequent source of confusion:
- Supply-chain
OrderStatus(@freya/core/supply-chain-types.ts):draft → placed → confirmed → in_production → quality_check → ready → shipped → delivered, withcancelled. This is the manufacturing/procurement order. - DTC fulfillment (
@freya/ecommerce/catalog-inventory.ts): a fourteen-state machineplaced → payment_confirmed → processing → picked → packed → shipped → out_for_delivery → delivered, plus return/refund states. - API customer order (
apps/freya/api/order-routes.ts): a compactPLACED → CONFIRMED → SHIPPED → DELIVERED → RETURNED,CANCELLEDfromPLACED/CONFIRMED. The handler validates transitions against avalidTransitionstable and rejects illegal moves.
These are not duplicates to be merged; they describe genuinely different processes (procuring stock, fulfilling a parcel, tracking a customer order).
Cross-Domain Boundaries#
- Freya vs. Aglaea. Supply-side vs. consumer-side. Freya publishes products, sizing, inventory, campaigns, and provenance; Aglaea consumes them. Freya never makes a per-consumer styling decision; no Freya library imports Aglaea.
- Freya vs. Cybele. Physical stores and factories are Cybele's; Freya models the business operations inside them. The Cybele connector builds store-design and factory-construction briefs.
- Freya vs. Brigid. Factory-automation machinery is Brigid's; Freya models execution and inspection at the batch/order level.
- Freya vs. Saraswati. Logistics and IoT for smart/tech products are Saraswati's; Freya consumes them when a product embeds devices.
- Freya vs. Maat / Asase. Freya produces brand/market/financial analytics
that Maat aggregates; Freya consumes Asase for natural-ingredient
traceability. All five integrations route through
@freya/connectors.
Today these boundaries are honored structurally (the connector is the only place that would reach across), but the connectors compute briefs locally rather than calling the peer domains.
Invariants, Failure Modes, and Extension Points#
Invariants. Capability libraries depend only on @freya/core, never on each
other (verified: the libraries import @freya/core and not sibling capability
packages). The business-unit catalog must hold exactly nineteen unique entries
(assertCompleteFreyaBusinessUnitCatalog). Fiber and INCI compositions must sum
to 100% ±0.5. Product/material provenance is meant to be append-only. State
machines reject illegal transitions by throwing rather than silently coercing.
Failure modes. Guards fail loud with typed FreyaError subclasses
(FREYA_VALIDATION_ERROR, FREYA_NOT_FOUND, FREYA_BUSINESS_RULE_VIOLATION)
rather than returning sentinels. The largest systemic gap is durability: every
store is an in-process Map, so state is lost on restart and is not shared
across processes — resetOrderStore()-style helpers exist precisely because
state is in-memory. Idempotency (a stated commerce requirement) is partially
modeled via reservation tokens and TTL soft-locks but is not yet enforced
through a transactional database.
Extension points. Adding a business unit means appending a profile to
FREYA_BUSINESS_UNIT_CATALOG, bumping the assertion, and adding a facade (or,
for a rich unit, a multi-module library) over @freya/core. New events are
added to the FreyaEventType union and routed in FREYA_EVENT_TOPICS. New gRPC
methods are appended to FREYA_GRPC_SERVICES and flow through
generateProtoStub. The clearest forward path for the domain is the wiring
phase: making the capability libraries import @freya/db, persisting through
the existing Drizzle schema, and standing up the already-specified
Kafka/gRPC/Redis surfaces.
Verification Expectations#
Each library and app ships a Vitest suite (*.spec.ts). Because state machines,
inventory accounting, AQL inspection, and financial calculations are the
domain's correctness core, their tests assert specific computed values against
known-correct answers, not just shape. Commerce, payment, inventory,
manufacturing, and customer-data changes additionally warrant contract
(@freya/contracts) and schema (@freya/db) regression coverage so the typed
boundary and the persistence definition stay consistent — even while the running
engines remain in-memory. Run with pnpm nx test <project> (or npx vitest run
directly from a library directory when Nx is contended by worktrees).