# Aglaea Domain — Technical Specifications

> **Aglaea** — AI-Powered Fashion, Beauty, and Personal Style Platform

This document specifies the implemented contracts of the Aglaea domain: the
foundation type system, domain entities, enumerations, the REST API surface, the
domain event system, persistence schemas, and configuration. Every type, enum
value, endpoint, and event below is drawn directly from the source under
`libs/aglaea/`.

Aglaea is a **pure library domain** — it has no `apps/aglaea/` or
`services/aglaea/`. It is implemented as 93 libraries under `libs/aglaea/`.

For a plain-English description of what each library does, see `features.md`.
For how they are layered and wired together, see `architecture.md`. This
document is the definitive contract reference — the source of truth for type
shapes, API paths, event strings, and schema field names.

---

## Table of Contents

1. [Foundation Type System (`@aglaea/core`)](#1-foundation-type-system-aglaeacore)
2. [Core Domain Constants (`@aglaea/core`)](#2-core-domain-constants-aglaeacore)
3. [Configuration (`@aglaea/core`)](#3-configuration-aglaeacore)
4. [AI Orchestration Types (`@aglaea/ai-orchestrator`)](#4-ai-orchestration-types-aglaeaai-orchestrator)
5. [REST API Surface (`@aglaea/api-services`)](#5-rest-api-surface-aglaeaapi-services)
6. [Domain Event System (`@aglaea/events`)](#6-domain-event-system-aglaeaevents)
7. [API-Services Event Catalog (`@aglaea/api-services`)](#7-api-services-event-catalog-aglaeaapi-services)
8. [Persistence Schemas (`@aglaea/database`)](#8-persistence-schemas-aglaeadatabase)
9. [SDK Surface (`@aglaea/sdk`)](#9-sdk-surface-aglaeasdk)
10. [Technology Stack](#10-technology-stack)

---

## 1. Foundation Type System (`@aglaea/core`)

`@aglaea/core` (`libs/aglaea/core/src/types.ts`) is the foundation type system
for the domain. Every composite type is defined as a Zod schema with an inferred
TypeScript type (`z.infer`), which means all types are validated at runtime
wherever they cross a module boundary. The package depends only on `zod`, so it
can be safely imported by any library in the domain without introducing
additional dependency weight.

The `index.ts` barrel re-exports `types`, `constants`, `validation`, `errors`,
`color-science`, `body-analysis`, `skin-analysis`, `hair-analysis`,
`fashion-taxonomy`, `style-profiling`, `recommendation-engine`,
`occasion-context`, `trend-analysis`, `fabric-intelligence`,
`fragrance-profiling`, `logging`, `config`, `feature-flags`, `rate-limiting`,
and `caching`.

### 1.1 Branded ID Types

Aglaea uses five nominal (branded) string types to prevent accidental mixing of
IDs across different entity types at compile time. Each is validated against a
UUID v4 pattern by its corresponding Zod schema.

| Type                | Schema                    | Purpose                                             |
| ------------------- | ------------------------- | --------------------------------------------------- |
| `PersonalProfileId` | `PersonalProfileIdSchema` | Identifier for a personal style profile             |
| `WardrobeItemId`    | `WardrobeItemIdSchema`    | Identifier for a wardrobe item                      |
| `OutfitId`          | `OutfitIdSchema`          | Identifier for a styled outfit                      |
| `AnalysisId`        | `AnalysisIdSchema`        | Identifier for an analysis session (skin/hair/etc.) |
| `RecommendationId`  | `RecommendationIdSchema`  | Identifier for a recommendation                     |

Each schema enforces `UUID_V4_PATTERN`
(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) and
returns a typed `z.ZodType` for the branded type.

### 1.2 Color Science Types

Colors are stored in multiple spaces simultaneously because different spaces
serve different purposes. RGB and Pantone are for display and production output;
HSL is for human-readable hue/saturation/lightness reasoning; LAB is the
canonical internal space for color-distance calculations and harmony scoring,
because LAB is perceptually uniform — equal numeric deltas correspond to equal
perceived differences.

| Schema               | Type           | Fields                                                                                  |
| -------------------- | -------------- | --------------------------------------------------------------------------------------- |
| `RGBSchema`          | `RGB`          | `r`, `g`, `b` — integers 0–255 (sRGB color space)                                       |
| `HSLSchema`          | `HSL`          | `h` (0–360 degrees), `s` (0–100), `l` (0–100)                                           |
| `LABSchema`          | `LAB`          | `L` (0–100), `a` (−128 to 127), `b` (−128 to 127) — CIE L\*a\*b\*, perceptually uniform |
| `CMYKSchema`         | `CMYK`         | `c`, `m`, `y`, `k` — percentages 0–100 (print production)                               |
| `PantoneColorSchema` | `PantoneColor` | `code` (Pantone reference), `name`, `hex` (6-digit hex approximation)                   |

The following enums capture color-system concepts used throughout the
recommendation engine:

**`ColorHarmonySchema`** (`ColorHarmony`) — color-wheel relationship enum, used
to score and label the palette harmony of an outfit recommendation:
`Complementary`, `Analogous`, `Triadic`, `SplitComplementary`, `Tetradic`,
`Monochromatic`.

**`SeasonalSubtypeSchema`** (`SeasonalSubtype`) — the 12-season color analysis
system, which refines the four parent seasons along warmth, value, and chroma
axes: `LightSpring`, `WarmSpring`, `ClearSpring`, `LightSummer`, `SoftSummer`,
`CoolSummer`, `SoftAutumn`, `WarmAutumn`, `DeepAutumn`, `DeepWinter`,
`CoolWinter`, `ClearWinter`.

**`SeasonSchema`** (`Season`) — parent season grouping: `Spring`, `Summer`,
`Autumn`, `Winter`.

**`SkinUndertoneSchema`** (`SkinUndertone`) — `Cool`, `Warm`, `Neutral`,
`Olive`.

**`ColorTemperatureSchema`** (`ColorTemperature`) — `Cool`, `Warm`, `Neutral`.

**`SeasonalColorPaletteSchema`** (`SeasonalColorPalette`) is the primary output
of color analysis and the primary input to outfit and accessory recommendations.
Its fields are:

| Field                    | Type              | Required | Meaning                                       |
| ------------------------ | ----------------- | -------- | --------------------------------------------- |
| `subtype`                | `SeasonalSubtype` | yes      | The 12-season subtype                         |
| `season`                 | `Season`          | yes      | Parent season grouping                        |
| `dominantCharacteristic` | `string`          | yes      | Primary characteristic (e.g. "Light", "Warm") |
| `bestColors`             | `LAB[]` (min 1)   | yes      | Core flattering palette                       |
| `goodColors`             | `LAB[]`           | yes      | Acceptable but non-optimal colors             |
| `avoidColors`            | `LAB[]`           | yes      | Colors that clash with or wash out the type   |
| `neutrals`               | `LAB[]` (min 1)   | yes      | Recommended neutral palette for staples       |
| `metalTones`             | `string[]`        | yes      | Best jewelry/hardware metal tones             |

### 1.3 Body Measurement Types

Body measurements are stored in centimeters. All ten fields are constrained to
physiologically plausible maximums (e.g., bust ≤ 300 cm) so that measurement
errors from photo analysis are caught at the schema boundary rather than
producing absurd downstream recommendations.

**`BodyMeasurementsSchema`** (`BodyMeasurements`) — all values in centimeters,
all positive:

| Field                | Constraint      | Meaning                                          |
| -------------------- | --------------- | ------------------------------------------------ |
| `bust`               | positive, ≤ 300 | Full bust circumference at widest point          |
| `waist`              | positive, ≤ 300 | Natural waist at narrowest point above the navel |
| `hip`                | positive, ≤ 300 | Hip circumference at widest point                |
| `inseam`             | positive, ≤ 150 | Inner leg from crotch to ankle                   |
| `shoulder`           | positive, ≤ 100 | Shoulder width across the back                   |
| `armLength`          | positive, ≤ 120 | Shoulder point to wrist bone                     |
| `torsoLength`        | positive, ≤ 100 | Base of neck to natural waist                    |
| `thighCircumference` | positive, ≤ 120 | Thigh circumference at widest point              |
| `neckCircumference`  | positive, ≤ 80  | Neck circumference at the base                   |
| `wristCircumference` | positive, ≤ 40  | Wrist circumference at narrowest point           |

**`BodyShapeSchema`** (`BodyShape`) — eight values: `Hourglass`, `Pear`,
`Apple`, `Rectangle`, `InvertedTriangle`, `Oval`, `Diamond`, `Athletic`. Each
enum entry carries a doc comment describing the measurement relationship that
defines it.

**`BodyProportionsSchema`** (`BodyProportions`) holds the ratios calculated from
raw measurements. These ratios — not the body shape label alone — determine
which visual-balance corrections are most appropriate, so two users with the
same body shape label but different ratios receive different silhouette
guidance.

| Field                  | Constraint    | Meaning                                   |
| ---------------------- | ------------- | ----------------------------------------- |
| `waistToHipRatio`      | positive, ≤ 2 | Waist-to-hip ratio (WHR)                  |
| `shoulderToHipRatio`   | positive, ≤ 3 | Upper/lower body balance                  |
| `bustToWaistRatio`     | positive, ≤ 3 | Upper-body definition                     |
| `torsoToLegRatio`      | positive, ≤ 3 | Whether torso or legs are longer          |
| `shoulderToWaistRatio` | positive, ≤ 3 | Influences neckline/top recommendations   |
| `bodyShape`            | `BodyShape`   | Body shape classification from the ratios |

**`SizeRegionSchema`** (`SizeRegion`) — `US`, `UK`, `EU`, `JP`, `AU`, `KR`.

**`ClothingSizeSchema`** (`ClothingSize`): `region` (`SizeRegion`), `category`
(string), `sizeValue` (string), optional `alphaSize`, optional `numericSize`,
optional `modifier` (`Petite` | `Regular` | `Tall` | `Plus`).

### 1.4 Skin Analysis Types

The skin analysis type system models both the categorical classification of skin
(type, tone, concerns) and the quantitative scoring of individual biomarkers.
The separation matters: categorical classifications drive routine logic
(sensitive skin gets different formulations than oily skin), while biomarker
scores enable longitudinal tracking (a user can see their hydration score
improving over time).

**`SkinTypeSchema`** (`SkinType`) — `Normal`, `Dry`, `Oily`, `Combination`,
`Sensitive`.

**`SkinConcernSchema`** (`SkinConcern`) — twelve concerns, each of which maps to
specific recommended active ingredients in `SKIN_CONCERN_INGREDIENTS`: `Acne`,
`Wrinkles`, `DarkSpots`, `LargePores`, `Dullness`, `Redness`, `Dehydration`,
`Hyperpigmentation`, `SunDamage`, `Texture`, `UndereyeCircles`, `Sagging`.

**`FitzpatrickScaleSchema`** (`FitzpatrickScale`) — `TypeI` through `TypeVI`,
the Fitzpatrick UV-response phototype scale. Used to determine recommended SPF
and burn-time guidance from `FITZPATRICK_UV_DATA`.

**`SensitivityLevelSchema`** (`SensitivityLevel`) — `Mild`, `Moderate`,
`Severe`. Used by `IngredientSensitivity` to record the severity of a known
reaction to a specific ingredient.

**`SkinAnalysisResultSchema`** (`SkinAnalysisResult`) is the output type of a
skin analysis run. It carries both the categorical classifications and eleven
quantitative biomarker scores, each on a 0–100 scale. Note the direction of
scoring: positive-attribute scores (hydration, firmness, clarity) read 100 =
best; negative-attribute scores (oiliness, redness) read 0 = none, 100 = severe.

Fields: `id` (`AnalysisId`), `analyzedAt` (datetime), `skinType` (`SkinType`),
`concerns` (`SkinConcern[]`), plus twelve quantitative scores each constrained
0–100: `hydration`, `oiliness`, `elasticity`, `pigmentation`, `texture`,
`wrinkleDepth`, `poreSize`, `redness`, `firmness`, `clarity`, `radiance`.

**`SkinToneSchema`** (`SkinTone`) — `fitzpatrickType` (`FitzpatrickScale`),
`undertone` (`SkinUndertone`), `labColor` (`LAB`), optional `hexApproximation`.

**`IngredientSensitivitySchema`** (`IngredientSensitivity`) — `ingredientName`
(string), `sensitivityLevel` (`SensitivityLevel`), optional `notes`.

### 1.5 Hair Analysis Types

Hair analysis types model the physical properties of hair that determine product
and routine compatibility. The Andre Walker curl-pattern system (1A–4C) is the
industry standard for curl classification, and porosity is the single most
important determinant of whether a product will moisturize or weigh down the
hair.

**`HairTypeSchema`** (`HairType`) — the Andre Walker curl-pattern system:
`Straight1A`, `Straight1B`, `Straight1C`, `Wavy2A`, `Wavy2B`, `Wavy2C`,
`Curly3A`, `Curly3B`, `Curly3C`, `Coily4A`, `Coily4B`, `Coily4C`.

**`HairDensitySchema`** (`HairDensity`) — `Fine`, `Medium`, `Thick` (strand
diameter classification).

**`HairPorositySchema`** (`HairPorosity`) — `Low`, `Medium`, `High`. Porosity
determines the balance between moisture-rich and protein-rich formulations a
hair type needs.

**`HairConditionSchema`** (`HairCondition`) — `Healthy`, `SlightlyDamaged`,
`Damaged`, `SeverelyDamaged`.

**`HairColorSchema`** (`HairColor`) — `naturalColor` (`LAB`), `currentColor`
(`LAB`), `isColorTreated` (boolean), `grayPercentage` (0–100), optional
`naturalLevel` (1–10), optional `currentLevel` (1–10).

**`HairAnalysisResultSchema`** (`HairAnalysisResult`) is the output type of a
hair analysis run: `id` (`AnalysisId`), `analyzedAt`, `hairType`, `density`,
`porosity`, `condition`, `color` (`HairColor`), plus four 0–100 scores:
`scalpHealth`, `moisture`, `elasticity`, `shine`.

### 1.6 Fashion Taxonomy Types

Fashion taxonomy provides the controlled vocabulary that every garment
classification and recommendation module uses to describe clothes. Without a
shared taxonomy, garment categories and style terms would be ad-hoc strings with
no consistent meaning across libraries.

**`GarmentCategorySchema`** (`GarmentCategory`) — twelve top-level categories:
`Tops`, `Bottoms`, `Dresses`, `Outerwear`, `Swimwear`, `Activewear`,
`Sleepwear`, `Underwear`, `Accessories`, `Shoes`, `Bags`, `Jewelry`.

**`GarmentSubcategorySchema`** (`GarmentSubcategory`) — a flat enum of
sub-categories grouped by parent category, including (among others)
`TShirt`/`ButtonDown`/`Blouse`/`Sweater` (Tops), `Jeans`/`Trousers`/`Skirt`
(Bottoms), `MiniDress`/`Gown`/`Jumpsuit` (Dresses), `Blazer`/`Coat`/`Trench`
(Outerwear), `Bikini`/`OnePiece` (Swimwear), `Sneakers`/`Heels`/`Boots` (Shoes),
`Tote`/`Crossbody`/`Clutch` (Bags), and `Necklace`/`Ring`/`Earrings` (Jewelry).

**`FabricTypeSchema`** (`FabricType`) — twenty fabric types: `Cotton`, `Silk`,
`Wool`, `Linen`, `Polyester`, `Nylon`, `Rayon`, `Cashmere`, `Denim`, `Leather`,
`Suede`, `Satin`, `Chiffon`, `Velvet`, `Tweed`, `Jersey`, `Lycra`, `Organza`,
`Tulle`, `Lace`.

**`FabricWeightSchema`** (`FabricWeight`) — `Sheer`, `Lightweight`, `Midweight`,
`Heavyweight`.

**`PatternSchema`** (`Pattern`) — fourteen patterns: `Solid`, `Stripes`,
`Plaid`, `Floral`, `PolkaDots`, `Geometric`, `Abstract`, `Paisley`,
`Houndstooth`, `Camo`, `AnimalPrint`, `Chevron`, `Gingham`, `TieDye`.

**`StyleAestheticSchema`** (`StyleAesthetic`) — fifteen aesthetics that classify
the dominant style identity of a garment or a user's preferences: `Classic`,
`Minimalist`, `Bohemian`, `Streetwear`, `Preppy`, `Romantic`, `Edgy`, `Sporty`,
`Glamorous`, `Vintage`, `AvantGarde`, `Scandinavian`, `Coastal`, `Western`,
`Gothic`.

**`GarmentConditionSchema`** (`GarmentCondition`) — `New`, `Excellent`, `Good`,
`Fair`, `Poor`.

**`SeasonSuitabilitySchema`** (`SeasonSuitability`) — four boolean flags:
`spring`, `summer`, `autumn`, `winter`.

**`WardrobeItemSchema`** (`WardrobeItem`) is the full wardrobe item entity. It
carries all attributes needed to generate outfit recommendations (category,
colors, fabric, formality, occasions, seasons) alongside administrative fields
for wardrobe management (purchase price, wear count, condition, storage).

| Field               | Type                 | Required       | Notes                                |
| ------------------- | -------------------- | -------------- | ------------------------------------ |
| `id`                | `WardrobeItemId`     | yes            | UUID v4                              |
| `profileId`         | `PersonalProfileId`  | yes            | Owning profile                       |
| `category`          | `GarmentCategory`    | yes            |                                      |
| `subcategory`       | `GarmentSubcategory` | yes            |                                      |
| `brand`             | `string`             | yes            |                                      |
| `primaryColor`      | `LAB`                | yes            | Primary color in LAB space           |
| `accentColors`      | `LAB[]`              | default `[]`   | Accent/secondary colors              |
| `fabric`            | `FabricType`         | yes            | Primary fabric                       |
| `fabricBlend`       | `FabricType[]`       | default `[]`   | Additional blend fabrics             |
| `fabricWeight`      | `FabricWeight`       | yes            |                                      |
| `pattern`           | `Pattern`            | yes            |                                      |
| `size`              | `ClothingSize`       | yes            |                                      |
| `styleAesthetic`    | `StyleAesthetic`     | yes            | Dominant aesthetic                   |
| `seasonSuitability` | `SeasonSuitability`  | yes            |                                      |
| `formality`         | `number` int 1–10    | yes            | 1 = extremely casual, 10 = black-tie |
| `condition`         | `GarmentCondition`   | yes            |                                      |
| `purchaseDate`      | `string` datetime    | optional       |                                      |
| `purchasePrice`     | `number` nonnegative | optional       |                                      |
| `currency`          | `string` length 3    | optional       | ISO 4217                             |
| `wearsCount`        | `number` int nonneg. | default `0`    | Total times worn                     |
| `lastWorn`          | `string` datetime    | optional       |                                      |
| `careInstructions`  | `string`             | optional       |                                      |
| `notes`             | `string`             | optional       |                                      |
| `imageUrl`          | `string` url         | optional       |                                      |
| `tags`              | `string[]`           | default `[]`   |                                      |
| `isAvailable`       | `boolean`            | default `true` | Not lent / in repair                 |
| `createdAt`         | `string` datetime    | yes            |                                      |
| `updatedAt`         | `string` datetime    | yes            |                                      |

### 1.7 Occasion and Context Types

Occasion and context types capture the circumstances for which an outfit is
being recommended. The outfit engine uses all of these fields together: occasion
determines formality bounds, dress code refines them, weather context gates
fabric and layering choices, and time of day affects color and formality
appropriateness.

**`OccasionTypeSchema`** (`OccasionType`) — fifteen occasions: `Casual`,
`Business`, `BusinessCasual`, `Formal`, `BlackTie`, `Cocktail`, `DateNight`,
`Wedding`, `Interview`, `Outdoor`, `Beach`, `Festival`, `Travel`, `Gym`,
`Lounge`.

**`WeatherContextSchema`** (`WeatherContext`) — `temperatureMin` /
`temperatureMax` (Celsius, −60 to 60), `humidity` (0–100), `precipitation`
(`None` | `Light Rain` | `Heavy Rain` | `Snow` | `Sleet` | `Drizzle`), `uvIndex`
(0–15), optional `windSpeed` (km/h, nonnegative), optional `description`.

**`DressCodeSchema`** (`DressCode`) — refines the formality band within an
occasion: `WhiteTie`, `BlackTie`, `Cocktail`, `BusinessFormal`,
`BusinessCasual`, `SmartCasual`, `Casual`, `Athleisure`, `Resort`.

**`TimeOfDaySchema`** (`TimeOfDay`) — `EarlyMorning`, `Morning`, `Afternoon`,
`Evening`, `Night`.

**`OutfitContextSchema`** (`OutfitContext`) — `occasion` (`OccasionType`),
optional `dressCode` (`DressCode`), `weather` (`WeatherContext`), optional
`location`, optional `setting` (`Indoor` | `Outdoor` | `Both`), `timeOfDay`
(`TimeOfDay`), optional `durationHours` (positive), optional `culturalContext`,
`specialRequirements` (`string[]`, default `[]`).

### 1.8 Recommendation Types

The recommendation types capture both the output of the outfit engine and the
multi-dimensional confidence scores that explain why an outfit was chosen.
Exposing confidence as seven distinct scores rather than one overall number lets
the user understand the tradeoffs — an outfit may score high on color harmony
but low on occasion fit.

**`RecommendationConfidenceSchema`** (`RecommendationConfidence`) — seven scores
each constrained 0–1: `overall`, `styleMatch`, `colorHarmony`, `occasionFit`,
`trendAlignment`, `bodyFlattery`, `weatherAppropriateness`.

**`StylingNoteSchema`** (`StylingNote`) — `category` (`Fit` | `Layering` |
`Accessories` | `Color` | `Proportions` | `Shoes` | `Hair` | `Makeup` |
`General`), `note` (string), `importance` (`Essential` | `Recommended` |
`Optional`).

**`OutfitRecommendationSchema`** (`OutfitRecommendation`) is the primary output
type of the outfit engine. It includes the wardrobe item IDs that compose the
outfit, the full multi-dimensional confidence score, optional swap suggestions
for any item in the outfit, and a human-readable rationale explaining the
recommendation.

| Field              | Type                                                   | Required     | Notes                                  |
| ------------------ | ------------------------------------------------------ | ------------ | -------------------------------------- |
| `id`               | `RecommendationId`                                     | yes          |                                        |
| `profileId`        | `PersonalProfileId`                                    | yes          |                                        |
| `items`            | `WardrobeItemId[]` (min 1)                             | yes          | Wardrobe items composing the outfit    |
| `context`          | `OutfitContext`                                        | yes          | Context the outfit was recommended for |
| `confidence`       | `RecommendationConfidence`                             | yes          | Multi-dimensional confidence           |
| `stylingNotes`     | `StylingNote[]`                                        | default `[]` |                                        |
| `colorHarmonyType` | `ColorHarmony`                                         | optional     | Harmony type achieved by the palette   |
| `rationale`        | `string`                                               | yes          | Human-readable reasoning               |
| `alternatives`     | array of `{ replaceItemId, alternativeIds[], reason }` | default `[]` | Swap suggestions                       |
| `generatedAt`      | `string` datetime                                      | yes          |                                        |

**`ShoppingRecommendationSchema`** (`ShoppingRecommendation`) — `id`,
`profileId`, `productName`, optional `brand`, `category` (`GarmentCategory`),
`subcategory` (`GarmentSubcategory`), `matchReasons` (`string[]`, min 1),
`priceMin` / `priceMax` (nonnegative), `currency` (length 3), `priorityScore`
(0–1), `confidence` (`RecommendationConfidence`), `pairsWith`
(`WardrobeItemId[]`, default `[]`), `styleAesthetic`, optional `suggestedColor`
(`LAB`), optional `suggestedFabric` (`FabricType`), optional `productUrl`,
optional `imageUrl`, `generatedAt`.

### 1.9 Trend Analysis Types

Trend types model the lifecycle and origin of fashion trends, enabling the
forecasting engine to distinguish between a micro-trend at emergence, a
macro-trend at peak, and a classic that has outlasted its original cycle.

**`TrendStatusSchema`** (`TrendStatus`) — `Emerging`, `Growing`, `Peak`,
`Declining`, `Classic`, `Revival`.

**`TrendCategorySchema`** (`TrendCategory`) — `Color`, `Silhouette`, `Pattern`,
`Fabric`, `Styling`, `Accessory`.

**`TrendOriginSchema`** (`TrendOrigin`) — `Runway`, `Street`, `Social`,
`Celebrity`, `Editorial`, `Subculture`.

**`FashionTrendSchema`** (`FashionTrend`) — `name`, `category`
(`TrendCategory`), `status` (`TrendStatus`), `relevanceScore` (0–1), `seasons`
(`Season[]`, min 1), `emergenceYear` (int 1900–2100), `origin` (`TrendOrigin`),
`description`, `alignedAesthetics` (`StyleAesthetic[]`, default `[]`),
`keyPieces` (`string[]`, default `[]`), `keyColors` (`LAB[]`, default `[]`),
`predictedLongevity` (`OneSeasonFad` | `MultiSeason` | `YearPlus` |
`EnduringSeveral` | `PermanentClassic`), `momentum` (`Accelerating` | `Stable` |
`Decelerating`), `lastUpdated`.

### 1.10 Fragrance Types

Fragrance types model the olfactory preference profile — preferred families,
individual notes, known avoidances, and occasion/season preferences — that the
fragrance matching engine uses to recommend perfumes without the user being able
to smell them first.

**`FragranceFamilySchema`** (`FragranceFamily`) — twelve families: `Floral`,
`Oriental`, `Woody`, `Fresh`, `Citrus`, `Aromatic`, `Chypre`, `Fougere`,
`Gourmand`, `Aquatic`, `Green`, `Musk`.

**`NotePositionSchema`** (`NotePosition`) — `Top`, `Middle`, `Base` (fragrance
pyramid position; top notes are detected first and fade fastest, base notes
linger longest).

**`FragranceNoteSchema`** (`FragranceNote`) — `ingredient` (string), `intensity`
(`NotePosition`), `description` (string).

**`FragranceProfileSchema`** (`FragranceProfile`) — `families`
(`FragranceFamily[]`, min 1), `preferredNotes` (`FragranceNote[]`, default
`[]`), `avoidances` (array of `{ ingredient, reason, details? }` where `reason`
is `Dislike` | `Allergy` | `Sensitivity` | `Migraine` | `Other`),
`occasionPreferences` (array of
`{ occasion, preferredFamilies, preferredIntensity }` where `preferredIntensity`
is `Light` | `Moderate` | `Strong` | `Statement`), optional
`preferredConcentration` (`EauFraiche` | `EauDeCologne` | `EauDeToilette` |
`EauDeParfum` | `Parfum`), `seasonalPreferences` (array of
`{ season, preferredFamilies }`).

### 1.11 Composite Profile Type

`PersonalStyleProfileSchema` (`PersonalStyleProfile`) is the root entity of the
domain — the aggregate that recommendation modules read from. It brings together
the outputs of every analysis engine into a single object. All analysis fields
are optional because the profile can be partially complete; the
`profileCompleteness` score (computed by `@aglaea/unified-profile`) tracks which
analyses are present.

| Field                     | Type                      | Required     | Meaning                        |
| ------------------------- | ------------------------- | ------------ | ------------------------------ |
| `id`                      | `PersonalProfileId`       | yes          |                                |
| `displayName`             | `string`                  | yes          |                                |
| `seasonalPalette`         | `SeasonalColorPalette`    | optional     | Seasonal color analysis result |
| `skinTone`                | `SkinTone`                | optional     |                                |
| `bodyMeasurements`        | `BodyMeasurements`        | optional     |                                |
| `bodyProportions`         | `BodyProportions`         | optional     | Calculated proportions         |
| `skinAnalysis`            | `SkinAnalysisResult`      | optional     | Latest skin analysis           |
| `ingredientSensitivities` | `IngredientSensitivity[]` | default `[]` |                                |
| `hairAnalysis`            | `HairAnalysisResult`      | optional     | Latest hair analysis           |
| `styleAesthetics`         | `StyleAesthetic[]`        | default `[]` | Ranked preferred aesthetics    |
| `sizes`                   | `ClothingSize[]`          | default `[]` | Preferred sizes by region      |
| `fragranceProfile`        | `FragranceProfile`        | optional     |                                |
| `createdAt`               | `string` datetime         | yes          |                                |
| `updatedAt`               | `string` datetime         | yes          |                                |

---

## 2. Core Domain Constants (`@aglaea/core`)

`constants.ts` provides reference data tables consumed by analysis and
recommendation modules. These are read-only lookup tables — they encode domain
knowledge (what SPF does Fitzpatrick Type III require? what ratio thresholds
classify a body as pear-shaped?) that would otherwise be hardcoded in individual
libraries. Centralizing them here ensures consistency and allows them to be
updated in one place.

- **Size conversion charts** — `WOMENS_SIZE_CHART`, `MENS_SIZE_CHART`,
  `WOMENS_SHOE_CHART`, `MENS_SHOE_CHART` (US/UK/EU mappings),
  `ALPHA_TO_US_WOMENS`, `ALPHA_TO_US_MENS` (alpha-to-numeric maps).
- **Measurement-to-size ranges** — `BUST_TO_US_SIZE`, `WAIST_TO_US_SIZE`,
  `HIP_TO_US_SIZE` (cm ranges mapped to US women's sizes).
- **Color theory constants** — `COLOR_HARMONY_ANGLES` (HSL wheel offsets per
  harmony type), `CIE_D65` / `CIE_D50` / `CIE_A` (illuminant white points),
  `SRGB_TO_XYZ_MATRIX`, `XYZ_TO_SRGB_MATRIX`, `LAB_EPSILON` (`0.008856`),
  `LAB_KAPPA` (`903.3`).
- **`FITZPATRICK_UV_DATA`** — per-phototype UV reaction data: minimum erythema
  dose range, burn-time range, recommended SPF, UV response description, melanin
  index range.
- **`FABRIC_CARE_INSTRUCTIONS`** — per-fabric care set: wash, dry, iron, notes,
  `maxWashTemp`, `dryerSafe`, `bleachSafe`.
- **`BODY_SHAPE_THRESHOLDS`** — ratio thresholds for body-shape classification
  (hourglass, pear, apple, rectangle, invertedTriangle, athletic, diamond,
  oval).
- **`IDEAL_PROPORTIONS`** — reference proportion ratios (golden waist-to-hip,
  balanced shoulder-to-hip, fashion-figure head ratio, torso-to-leg ratio).
- **`SEASONAL_PALETTE_ANCHORS`** — per-`SeasonalSubtype` anchor data: parent
  season, dominant characteristic, anchor `bestColors`/`neutrals`/`avoidColors`
  in LAB, and `metalTones`.
- **`SUBCATEGORY_FORMALITY`** — default formality score (1–10) per garment
  subcategory.
- **`SUBCATEGORY_TO_CATEGORY`** — maps each subcategory to its parent category.
- **`LAYERING_ORDER`** — stacking order (0 = innermost) per garment category.
- **`FRAGRANCE_NOTE_TIMING`** — top/middle/base note onset, peak, fade, and
  duration (minutes); **`FRAGRANCE_CONCENTRATION_LONGEVITY`** — longevity hours
  and oil-percentage ranges per concentration tier.
- **`PATTERN_COMPATIBILITY`** — per-pattern set of mix-compatible patterns.
- **`FABRIC_SEASONAL_SCORES`** — fabric suitability score (0–1) per season.
- **`SKIN_CONCERN_INGREDIENTS`** — per-`SkinConcern` recommended ingredient list
  with a description.

---

## 3. Configuration (`@aglaea/core`)

`config.ts` defines `AglaeaConfigSchema` (`AglaeaConfig`), a section-oriented,
Zod-validated configuration object. Every section has defaults, so `{}` parses
to a valid config — libraries never have to handle a missing config section.
Config values can be overridden at startup via direct object overrides or via
environment variables, following the `AGLAEA_<SECTION>_<KEY>` naming convention
(e.g., `AGLAEA_COLOR_ANALYSIS_ENABLED=false`).

The configuration sections and their key fields are:

| Section           | Schema                        | Key fields (with defaults)                                                                                                                           |
| ----------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `colorAnalysis`   | `ColorAnalysisConfigSchema`   | `enabled` (true), `defaultIlluminant` (`D65`), `ciede2000Threshold` (2.0), `seasonalAnalysisVersion` (1)                                             |
| `skinAnalysis`    | `SkinAnalysisConfigSchema`    | `enabled` (true), `minConfidenceScore` (0.7), `biomarkerCount` (12), `fitzpatrickAccuracy` (0.95)                                                    |
| `hairAnalysis`    | `HairAnalysisConfigSchema`    | `enabled` (true), `walkerClassificationVersion` (2)                                                                                                  |
| `bodyAnalysis`    | `BodyAnalysisConfigSchema`    | `enabled` (true), `sizeConversionRegions` (`['US','UK','EU']`)                                                                                       |
| `recommendations` | `RecommendationsConfigSchema` | `enabled` (true), `maxResults` (20), `minConfidenceThreshold` (0.5), `enableAIEnhancement` (false), `trendWeight` (0.3), `personalStyleWeight` (0.5) |
| `trends`          | `TrendsConfigSchema`          | `enabled` (true), `forecastHorizonDays` (90), `minDataPointsForPrediction` (30), `adoptionCurveModel` (`rogers`)                                     |
| `fragrance`       | `FragranceConfigSchema`       | `enabled` (true), `maxRecommendations` (10), `seasonalBonus` (0.15)                                                                                  |
| `cache`           | `CacheConfigSchema`           | `defaultTTLMs` (300000), `skinAnalysisTTLMs` (1800000), `trendDataTTLMs` (300000), `colorPaletteTTLMs` (3600000)                                     |
| `rateLimit`       | `RateLimitConfigSchema`       | `enabled` (true), `defaultWindowMs` (60000), `defaultMaxRequests` (100)                                                                              |

**Loading and accessors.** `loadAglaeaConfig(overrides?)` merges, in priority
order: schema defaults, caller-supplied overrides, and environment-variable
overrides. Environment variables use a flat `AGLAEA_<SECTION>_<KEY>` format
(example: `AGLAEA_COLOR_ANALYSIS_ENABLED=false` →
`{ colorAnalysis: { enabled: false } }`). `validateAglaeaConfig(config)` returns
a `safeParse`-style result. `getConfigSection(config, section)` is a type-safe
section accessor. `mergeConfigs(base, override)` deep-merges two partials
(arrays replace, not concatenate). `ConfigManager` is a stateful container with
`getAll`/`get`/`set`/`reset` and change-subscription support.

---

## 4. AI Orchestration Types (`@aglaea/ai-orchestrator`)

`libs/aglaea/ai-orchestrator/src/types.ts` defines the contracts that every
library uses when requesting ML inference. The library ships `orchestrator`,
`model-registry`, `ab-testing`, `fallback`, `inference-queue`,
`batch-inference`, `monitoring`, `cost-tracker`, `warmup`, and `edge-deployment`
modules — the types below are shared across all of them.

**Branded type.** `ModelId` (`Brand<string, 'ModelId'>`), constructed via
`modelId(raw)`.

**`ModelProvider`** (enum) — the set of supported ML inference providers:
`OpenAI`, `Anthropic`, `HuggingFace`, `Custom`, `RunPod`, `Replicate`, `Local`.

**`ModelCapability`** (enum) — the ten capabilities a model can be registered
for: `SkinAnalysis`, `HairAnalysis`, `BodyAnalysis`, `ColorAnalysis`,
`OutfitRecommendation`, `TrendForecasting`, `FragranceMatching`, `VirtualTryOn`,
`FabricDetection`, `StyleClassification`.

**`ModelConfig`** — the full descriptor of a registered model: `id` (`ModelId`),
`name`, `provider` (`ModelProvider`), `endpoint`, optional `apiKey` (in-memory
only, never persisted), `capabilities` (`ModelCapability[]`), `version`,
`maxBatchSize`, `timeoutMs`, `costPerInference` (USD), `isActive`,
`warmupRequired`, optional `metadata`.

**`InferencePriority`** — `low` | `normal` | `high` | `critical`. The helper
`priorityWeight(p)` maps these to numeric weights (`critical` 4 → `low` 1) for
the priority queue.

**`InferenceRequest<T>`** — `id`, `modelId`, `input`, `priority`, `timestamp`,
optional `metadata`. **`InferenceResult<T>`** — `requestId`, `modelId`,
`output`, `latencyMs`, `cost`, `cached`, `modelVersion`, `timestamp`.
**`InferenceError`** — `requestId`, `modelId`, `error`, `code`, `retryable`.

**`ABTestConfig`** — `id`, `name`, `controlModelId`, `treatmentModelId`,
`trafficSplitPercent` (0–100), `startDate`, optional `endDate`, `capability`,
`isActive`. **`ABTestResult`** — `testId`, `controlMetrics`, `treatmentMetrics`,
optional `winner` (`control` | `treatment`), `confidence`.

**`ModelMetrics`** — per-model performance tracking: `totalRequests`,
`successRate`, `avgLatencyMs`, `p95LatencyMs`, `p99LatencyMs`, `avgCost`,
`totalCost`, `errorRate`.

**`QueuedInference`** — wraps a request with its `resolve`/`reject` callbacks
and `enqueuedAt`. **`QueueStats`** — `processed`, `failed`, `queued`,
`avgWaitMs`.

**Edge deployment.** `TargetDevice` (`cpu` | `gpu` | `tpu` | `npu`),
`Quantization` (`none` | `int8` | `int4` | `fp16`), `EdgeDeploymentConfig`
(`modelId`, `targetDevice`, `quantization`, `maxMemoryMb`, `batchSize`,
`warmupSamples`).

**Warm-up.** `WarmupResult` (`modelId`, `success`, `latencyMs`, optional
`error`), `WarmupStatus` (`cold` | `warming` | `warm` | `failed`).

---

## 5. REST API Surface (`@aglaea/api-services`)

`@aglaea/api-services` defines the REST API as typed `RouteDefinition` objects
grouped by domain module, aggregated by `api-registry.ts`. The registry constant
`API_VERSION` is `v1` and `BASE_PATH` is `/api/v1`. All endpoint paths use
colon-prefixed path parameters (e.g. `:profileId`).

A `RouteDefinition` carries `method`, `path`, `summary`, `tags`, optional
`requestBody` (a `SchemaRef` with `description`, `contentType`, `schema`),
optional `queryParams` / `pathParams` (maps of `ParamDef`), a `responses` map
(HTTP status → `ResponseDef`), an `auth` field (`required` | `optional` |
`none`), and an optional `rateLimit` (`{ windowMs, max }`).

The registry aggregates **64 endpoints** across seven modules. All endpoints
require authentication (`auth: 'required'`) except the eight Trends/Discovery
endpoints noted below.

### 5.1 Profile Endpoints (`profile-endpoints.ts`)

These ten endpoints cover the full lifecycle of a personal style profile —
creation, retrieval, update, deletion, photo upload, preference management,
history browsing, and external sync.

| Method | Path                                      | Purpose                                        | Auth |
| ------ | ----------------------------------------- | ---------------------------------------------- | ---- |
| GET    | `/api/v1/profiles/:profileId`             | Get personal profile by ID                     | req. |
| POST   | `/api/v1/profiles`                        | Create a new personal profile                  | req. |
| PUT    | `/api/v1/profiles/:profileId`             | Update an existing profile                     | req. |
| DELETE | `/api/v1/profiles/:profileId`             | Delete a profile and all associated data       | req. |
| GET    | `/api/v1/profiles/:profileId/summary`     | Get condensed profile summary with key metrics | req. |
| POST   | `/api/v1/profiles/:profileId/photo`       | Upload or replace the profile photo            | req. |
| GET    | `/api/v1/profiles/:profileId/preferences` | Get style/notification/privacy preferences     | req. |
| PUT    | `/api/v1/profiles/:profileId/preferences` | Update user preferences                        | req. |
| GET    | `/api/v1/profiles/:profileId/history`     | Get paginated activity history                 | req. |
| POST   | `/api/v1/profiles/:profileId/sync`        | Trigger a full sync from external sources      | req. |

### 5.2 Analysis Endpoints (`analysis-endpoints.ts`)

These ten endpoints submit photos or measurements for analysis and retrieve
results. The six analysis-submission endpoints (`POST /analysis/{type}`) accept
`multipart/form-data` and respond `202` with an `AnalysisJob` schema — analysis
is asynchronous. They enforce per-minute rate limits (15/min for
skin/color/face/hair/nails, 10/min for body).

| Method | Path                                  | Purpose                                               | Auth |
| ------ | ------------------------------------- | ----------------------------------------------------- | ---- |
| POST   | `/api/v1/analysis/skin`               | Run a comprehensive skin analysis                     | req. |
| POST   | `/api/v1/analysis/color`              | Run seasonal and undertone color analysis             | req. |
| POST   | `/api/v1/analysis/face-shape`         | Run face shape detection and proportion analysis      | req. |
| POST   | `/api/v1/analysis/body`               | Run body shape and proportion analysis                | req. |
| POST   | `/api/v1/analysis/hair`               | Run hair type, texture, and condition analysis        | req. |
| POST   | `/api/v1/analysis/nails`              | Run nail shape, health, and color analysis            | req. |
| GET    | `/api/v1/analysis/:analysisId`        | Retrieve a completed analysis result by ID            | req. |
| GET    | `/api/v1/analysis/history/:profileId` | Get paginated analysis history (filterable by type)   | req. |
| POST   | `/api/v1/analysis/compare`            | Compare two analyses (before/after) to track progress | req. |
| DELETE | `/api/v1/analysis/:analysisId`        | Delete an analysis result and its images              | req. |

### 5.3 Recommendation Endpoints (`recommendation-endpoints.ts`)

These ten endpoints generate personalized recommendations across the platform's
major recommendation types — outfit, shopping, style tips, color palette,
skincare routine, hair care, and makeup. All are `POST` requests because they
consume context (occasion, weather, skin analysis ID) from the request body.

| Method | Path                                         | Purpose                                             | Auth |
| ------ | -------------------------------------------- | --------------------------------------------------- | ---- |
| POST   | `/api/v1/recommendations/outfit`             | Generate outfit recommendations for an occasion     | req. |
| POST   | `/api/v1/recommendations/shopping`           | Get shopping recommendations to fill wardrobe gaps  | req. |
| POST   | `/api/v1/recommendations/style-tips`         | Get personalized style tips                         | req. |
| POST   | `/api/v1/recommendations/color-palette`      | Generate a personalized color palette               | req. |
| POST   | `/api/v1/recommendations/skincare-routine`   | Generate a skincare routine from skin analysis      | req. |
| POST   | `/api/v1/recommendations/hair-care`          | Generate a hair care routine from hair analysis     | req. |
| POST   | `/api/v1/recommendations/makeup-look`        | Generate a makeup look from face shape and coloring | req. |
| GET    | `/api/v1/recommendations/:recId`             | Get detailed recommendation by ID                   | req. |
| POST   | `/api/v1/recommendations/:recId/feedback`    | Submit feedback on a recommendation                 | req. |
| GET    | `/api/v1/recommendations/history/:profileId` | Get paginated recommendation history                | req. |

### 5.4 Wardrobe Endpoints (`wardrobe-endpoints.ts`)

These ten endpoints manage the digital wardrobe — adding, updating, and removing
items, logging wear events, retrieving analytics, identifying gaps, and
generating a capsule wardrobe from existing inventory.

| Method | Path                                             | Purpose                                         | Auth |
| ------ | ------------------------------------------------ | ----------------------------------------------- | ---- |
| GET    | `/api/v1/wardrobe/:profileId`                    | Get the full wardrobe inventory                 | req. |
| POST   | `/api/v1/wardrobe/:profileId/items`              | Add a new item to the wardrobe                  | req. |
| PUT    | `/api/v1/wardrobe/:profileId/items/:itemId`      | Update an existing wardrobe item                | req. |
| DELETE | `/api/v1/wardrobe/:profileId/items/:itemId`      | Remove an item from the wardrobe                | req. |
| POST   | `/api/v1/wardrobe/:profileId/items/:itemId/wear` | Log that an item was worn on a date             | req. |
| GET    | `/api/v1/wardrobe/:profileId/analytics`          | Get analytics including cost-per-wear           | req. |
| GET    | `/api/v1/wardrobe/:profileId/gaps`               | Identify wardrobe gaps                          | req. |
| POST   | `/api/v1/wardrobe/:profileId/capsule`            | Generate a capsule wardrobe from existing items | req. |
| GET    | `/api/v1/wardrobe/:profileId/outfits`            | Get outfit history with wear dates and ratings  | req. |
| POST   | `/api/v1/wardrobe/:profileId/digitize`           | Digitize a clothing item from a photo (AI)      | req. |

### 5.5 Virtual Try-On Endpoints (`tryon-endpoints.ts`)

These eight endpoints submit try-on requests for different item types and
retrieve results. Try-on sessions are persistent — a session ID is returned
immediately, and the rendered result is retrieved asynchronously.

| Method | Path                        | Purpose                                           | Auth |
| ------ | --------------------------- | ------------------------------------------------- | ---- |
| POST   | `/api/v1/tryon/fashion`     | Virtually try on a clothing item (body-mapped)    | req. |
| POST   | `/api/v1/tryon/makeup`      | Virtually try on a makeup look (face-mapped AR)   | req. |
| POST   | `/api/v1/tryon/hair`        | Virtually try on a hairstyle or hair color        | req. |
| POST   | `/api/v1/tryon/nails`       | Virtually try on a nail design or color           | req. |
| POST   | `/api/v1/tryon/accessories` | Virtually try on glasses, jewelry, or hats        | req. |
| POST   | `/api/v1/tryon/avatar`      | Generate a 3D avatar from profile data and photos | req. |
| GET    | `/api/v1/tryon/:sessionId`  | Get the result of a try-on session                | req. |
| DELETE | `/api/v1/tryon/:sessionId`  | Delete a try-on session and its rendered assets   | req. |

### 5.6 Conversation Endpoints (`conversation-endpoints.ts`)

These eight endpoints drive multi-turn dialogue with the AI style assistant.
Sessions are stateful — each message is sent to an existing session so the
assistant can maintain context across turns.

| Method | Path                                           | Purpose                                          | Auth |
| ------ | ---------------------------------------------- | ------------------------------------------------ | ---- |
| POST   | `/api/v1/conversations`                        | Start a conversation with the AI style assistant | req. |
| POST   | `/api/v1/conversations/:sessionId/message`     | Send a message and receive a response            | req. |
| GET    | `/api/v1/conversations/:sessionId`             | Retrieve the full message history                | req. |
| DELETE | `/api/v1/conversations/:sessionId`             | End and archive a conversation session           | req. |
| GET    | `/api/v1/conversations/:sessionId/suggestions` | Get contextual follow-up suggestions             | req. |
| POST   | `/api/v1/conversations/:sessionId/feedback`    | Rate a specific assistant response               | req. |
| GET    | `/api/v1/conversations/active`                 | List all active conversations for the user       | req. |
| POST   | `/api/v1/conversations/:sessionId/context`     | Update the conversation context                  | req. |

### 5.7 Trends and Discovery Endpoints (`trends-endpoints.ts`)

These eight endpoints are the only ones in the registry that do not require
authentication for all callers — trend and discovery data can be surfaced to
anonymous users for browsing. Personalized endpoints (`/personalized/:profileId`
and `/forecast`) require authentication because they depend on profile data.

| Method | Path                                     | Purpose                                            | Auth     |
| ------ | ---------------------------------------- | -------------------------------------------------- | -------- |
| GET    | `/api/v1/trends`                         | Get current trending styles, colors, silhouettes   | optional |
| GET    | `/api/v1/trends/:trendId`                | Get detailed information about a trend             | optional |
| GET    | `/api/v1/trends/personalized/:profileId` | Get trends personalized to a profile               | required |
| GET    | `/api/v1/trends/forecast`                | Get trend forecasts for upcoming seasons           | required |
| GET    | `/api/v1/discovery/inspiration`          | Get a curated inspiration feed                     | optional |
| GET    | `/api/v1/discovery/celebrities/:celebId` | Get style profile and looks for a celebrity        | optional |
| GET    | `/api/v1/discovery/communities`          | List style communities and interest groups         | optional |
| POST   | `/api/v1/discovery/search`               | Search across trends, looks, products, communities | optional |

---

## 6. Domain Event System (`@aglaea/events`)

`@aglaea/events` provides the typed domain event system that decouples Aglaea's
modules. When an analysis completes, it publishes a typed event; the
skincare-routine library subscribes to that event and updates its
recommendations without the analysis library needing to know it exists. This
decoupling is what allows 93 libraries to coordinate without forming a tightly
coupled dependency web.

The library ships `publisher`, `consumer`, `dead-letter`, `replay`, `analytics`,
and `helpers` modules over the core types below.

### 6.1 Core Event Types

These types form the envelope that wraps every domain event, regardless of
payload type.

- **`DomainEvent<T>`** — `id` (UUID v4), `type` (dot-namespaced), `timestamp`
  (ISO-8601), `version` (schema version number), `source` (originating context),
  optional `correlationId`, optional `causationId`, optional `metadata`,
  `payload` (typed `T`).
- **`EventEnvelope<T>`** — wraps a `DomainEvent<T>` with `routingKey`, optional
  `partition`, optional `deduplicationId`.
- **`EventHandler<T>`** — `(event: DomainEvent<T>) => void | Promise<void>`.
- **`EventFilter`** — optional `source`, `correlationId`, `metadata` predicate.
- **`EventSubscription`** — `id`, `eventType`, `handler`, optional `filter`.
- **`EventPublisherOptions`** — optional `retries`, `timeout`.
- **`DeadLetterEntry<T>`** — `event`, `error`, `attemptCount`, `lastAttempt`,
  optional `nextRetry`.
- **`ReplayFilter`** — optional `fromTimestamp`, `toTimestamp`, `eventTypes`,
  `source`, `correlationId`, `limit`, `offset`.

### 6.2 Event Type Constants (`AGLAEA_EVENT_TYPES`)

The `AGLAEA_EVENT_TYPES` const object defines 23 event type strings grouped by
domain area. `AglaeaEventType` is the TypeScript union of all values, which
means any switch statement on an event type will produce a compile error if a
case is missed.

| Constant                   | Type string                                  | Payload interface               |
| -------------------------- | -------------------------------------------- | ------------------------------- |
| `SKIN_ANALYSIS_COMPLETED`  | `aglaea.analysis.skin-completed`             | `SkinAnalysisCompletedPayload`  |
| `BODY_ANALYSIS_COMPLETED`  | `aglaea.analysis.body-completed`             | `BodyAnalysisCompletedPayload`  |
| `HAIR_ANALYSIS_COMPLETED`  | `aglaea.analysis.hair-completed`             | `HairAnalysisCompletedPayload`  |
| `COLOR_ANALYSIS_COMPLETED` | `aglaea.analysis.color-completed`            | `ColorAnalysisCompletedPayload` |
| `ANALYSIS_FAILED`          | `aglaea.analysis.failed`                     | `AnalysisFailedPayload`         |
| `OUTFIT_RECOMMENDED`       | `aglaea.recommendation.outfit-recommended`   | `OutfitRecommendedPayload`      |
| `SHOPPING_RECOMMENDED`     | `aglaea.recommendation.shopping-recommended` | `ShoppingRecommendedPayload`    |
| `STYLE_TIP_GENERATED`      | `aglaea.recommendation.style-tip-generated`  | `StyleTipGeneratedPayload`      |
| `RECOMMENDATION_ACCEPTED`  | `aglaea.recommendation.accepted`             | `RecommendationAcceptedPayload` |
| `RECOMMENDATION_REJECTED`  | `aglaea.recommendation.rejected`             | `RecommendationRejectedPayload` |
| `WARDROBE_ITEM_ADDED`      | `aglaea.wardrobe.item-added`                 | `WardrobeItemAddedPayload`      |
| `WARDROBE_ITEM_REMOVED`    | `aglaea.wardrobe.item-removed`               | `WardrobeItemRemovedPayload`    |
| `WARDROBE_ITEM_WORN`       | `aglaea.wardrobe.item-worn`                  | `WardrobeItemWornPayload`       |
| `WARDROBE_ITEM_UPDATED`    | `aglaea.wardrobe.item-updated`               | `WardrobeItemUpdatedPayload`    |
| `OUTFIT_CREATED`           | `aglaea.wardrobe.outfit-created`             | `OutfitCreatedPayload`          |
| `OUTFIT_RATED`             | `aglaea.wardrobe.outfit-rated`               | `OutfitRatedPayload`            |
| `PURCHASE_COMPLETED`       | `aglaea.purchase.completed`                  | `PurchaseCompletedPayload`      |
| `WISHLIST_ADDED`           | `aglaea.purchase.wishlist-added`             | `WishlistAddedPayload`          |
| `CART_ABANDONED`           | `aglaea.purchase.cart-abandoned`             | `CartAbandonedPayload`          |
| `RETURN_INITIATED`         | `aglaea.purchase.return-initiated`           | `ReturnInitiatedPayload`        |
| `PROFILE_FOLLOWED`         | `aglaea.social.profile-followed`             | `ProfileFollowedPayload`        |
| `OUTFIT_SHARED`            | `aglaea.social.outfit-shared`                | `OutfitSharedPayload`           |
| `STYLE_BOARD_CREATED`      | `aglaea.social.style-board-created`          | `StyleBoardCreatedPayload`      |
| `STYLE_BOARD_LIKED`        | `aglaea.social.style-board-liked`            | `StyleBoardLikedPayload`        |

### 6.3 Event Payloads

The following payload shapes illustrate the data carried by the most commonly
consumed events. All payload types are defined in `events.ts` and are exported
alongside their event constants.

- **`SkinAnalysisCompletedPayload`** — `profileId`, `analysisId`, `skinType`
  (`oily` | `dry` | `combination` | `normal` | `sensitive`), `fitzpatrickType`
  (1–6), `overallScore`, `concerns` (`SkinConcern[]` where each is
  `{ name, severity: 'low' | 'moderate' | 'high' }`), `timestamp`.
- **`BodyAnalysisCompletedPayload`** — `profileId`, `analysisId`, `bodyShape`
  (`hourglass` | `pear` | `apple` | `rectangle` | `inverted-triangle`),
  `measurements` (`MeasurementsSummary`: optional `bust`, `waist`, `hips`,
  `height`, `weight`, `inseam`).
- **`HairAnalysisCompletedPayload`** — `profileId`, `analysisId`, `hairType`
  (`1a`–`4c`), `healthScore`.
- **`ColorAnalysisCompletedPayload`** — `profileId`, `analysisId`,
  `seasonalSubtype` (12-value kebab-case union), `undertone` (`warm` | `cool` |
  `neutral`).
- **`OutfitRecommendedPayload`** — `profileId`, `recommendationId`, `itemIds`,
  `confidenceScore`, `occasion`.
- **`WardrobeItemAddedPayload`** — `profileId`, `itemId`, `category`, `brand`.
- **`PurchaseCompletedPayload`** — `profileId`, `productId`, `price`,
  `retailer`, `category`.

---

## 7. API-Services Event Catalog (`@aglaea/api-services`)

Separately from `@aglaea/events`, `@aglaea/api-services` declares **39
`EventDefinition` records** intended for documentation and contract generation,
aggregated by `api-registry.ts`. These records describe the same domain events
from the perspective of the API contract surface — each `EventDefinition`
carries `type` (dot identifier), `description`, `payload` (schema name), `topic`
(message-bus topic), and `version`. They use a different naming scheme from the
`@aglaea/events` constants and are not the runtime event type strings.

| Module file                | Count | Topics                                                                                                                                                            |
| -------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `analysis-events.ts`       | 10    | `analysis.skin`, `analysis.color`, `analysis.face`, `analysis.body`, `analysis.hair`, `analysis.nails`, `analysis.progress`, `analysis.batch`, `analysis.profile` |
| `recommendation-events.ts` | 10    | `recommendations.{outfit,shopping,routine,feedback,lifecycle,trends,tips,batch}`                                                                                  |
| `wardrobe-events.ts`       | 10    | `wardrobe.{items,wear,outfits,analytics,capsule}`                                                                                                                 |
| `social-events.ts`         | 10    | `social.{outfits,communities,challenges,achievements,experts,feed}`                                                                                               |

Representative event types from the catalog: `skin.analysis.completed`,
`color.analysis.completed`, `outfit.recommended`, `routine.recommended`,
`trend.alert.generated`, `wardrobe.item.added`, `wardrobe.gap.detected`,
`wardrobe.capsule.generated`, `outfit.shared`, `community.joined`,
`challenge.completed`, `achievement.unlocked`, `badge.earned`.

---

## 8. Persistence Schemas (`@aglaea/database`)

`@aglaea/database` defines the persistence layer. It uses **Knex** for SQL
migrations and **Zod** schemas for table row validation. There is no Prisma in
this domain — the direct Knex + Zod pattern was chosen to keep the persistence
layer free of ORM magic and to share the same Zod validation approach as the
rest of `@aglaea/core`. The target store is PostgreSQL. Dependencies are
minimal: `knex`, `zod`, and `uuid`.

### 8.1 Migrations

Migrations are Knex `up`/`down` functions, re-exported from
`migrations/index.ts`. They run in sequence; each migration builds on the
previous one.

1. `20260318000001_initial_setup` — creates the `uuid-ossp` and `pg_trgm`
   PostgreSQL extensions, the custom enum types (e.g. `seasonal_subtype`,
   `skin_undertone`, `color_temperature`, `gender_type`), and the shared
   `updated_at` trigger function.
2. `20260318000002_core_tables` — core domain tables (profiles, analysis
   records, wardrobe, outfits, recommendations).
3. `20260318000003_catalog_tables` — product and catalog tables (products,
   trends, ingredients, fabrics, brands, retailers).
4. `20260318000004_social_audit` — social and audit tables (follows, shares,
   style boards, audit log).
5. `20260318000005_seed_data` — reference data seeding.

### 8.2 Schema Modules

The `schema/index.ts` barrel re-exports sixteen schema modules. Each module
exports a full record schema plus `CreateInput` and (for mutable entities)
`UpdateInput` variants derived via `.omit()` / `.partial()`:

`common`, `profiles`, `skin-analysis`, `body-measurements`, `wardrobe`,
`outfits`, `products`, `recommendations`, `preferences`, `feedback`, `trends`,
`ingredients`, `fabrics`, `brands`, `social`, `audit`.

`common.ts` provides shared building blocks used by all other modules:
`UuidSchema`, `TimestampSchema`, `HexColorSchema`, `UrlSchema`,
`CurrencyCodeSchema`, `PaginationSchema`, `SortDirectionSchema`, `SortSchema`,
`DateRangeFilterSchema`, `NumericRangeFilterSchema`, `JsonValueSchema`,
`JsonObjectSchema`, `StringArrayJsonSchema`.

### 8.3 `PersonalProfile` (`profiles.ts`)

`PersonalProfileSchema` is the database-layer representation of a user's
profile. It carries less data than `PersonalStyleProfile` from `@aglaea/core`
because analysis results are stored in their own tables and joined when needed,
rather than embedded in the profile row.

`PersonalProfileSchema` — `id` (UUID), `userId` (UUID, FK to auth system),
`displayName`, `email`, optional `dateOfBirth` (`YYYY-MM-DD`), optional `gender`
(`GenderSchema`: `female` | `male` | `non_binary` | `prefer_not_to_say` |
`other`), optional `location` (`LocationSchema`: `city`, `country` ISO 3166-1
alpha-2, `timezone` IANA), optional `profileImageUrl`, optional
`seasonalSubtype` (`SeasonalSubtypeDbSchema`, 12-value), optional
`skinUndertone` (`Cool` | `Warm` | `Neutral` | `Olive`), optional
`colorTemperature` (`Cool` | `Warm` | `Neutral`), `createdAt`, `updatedAt`.

### 8.4 `SkinAnalysisRecord` (`skin-analysis.ts`)

Each analysis run produces a dated history row, not an overwrite, so the full
history of a user's skin condition is reconstructable over time. The eleven
dimension scores here correspond exactly to those in `SkinAnalysisResultSchema`
from `@aglaea/core`.

`SkinAnalysisRecordSchema` — `id`, `profileId` (FK), `analysisDate`, `skinType`
(`SkinTypeDbSchema`), optional `fitzpatrickType`, optional `undertone`, eleven
0–100 dimension scores (`hydrationScore`, `oilinessScore`, `elasticityScore`,
`pigmentationScore`, `textureScore`, `wrinkleDepthScore`, `poreSizeScore`,
`rednessScore`, `firmnessScore`, `clarityScore`, `radianceScore`) plus a
computed `overallScore`, `concerns` (`SkinConcernDb[]`, default `[]`),
`recommendations` (`string[]`, default `[]`), optional `imageUrl`, optional
`metadata` (JSON), `createdAt`. The module also exports
`IngredientSensitivityRecordSchema`.

### 8.5 `BodyMeasurementRecord` (`body-measurements.ts`)

Like skin analysis, body measurements are stored as dated history rows. All
measurement fields are optional because not every photo submission produces all
ten measurements.

`BodyMeasurementRecordSchema` — `id`, `profileId` (FK), `measurementDate`, ten
optional cm circumference/length fields (`bustCm`, `waistCm`, `hipCm`,
`inseamCm`, `shoulderWidthCm`, `armLengthCm`, `torsoLengthCm`,
`thighCircumferenceCm`, `neckCircumferenceCm`, `wristCircumferenceCm`), optional
`heightCm` and `weightKg`, optional `bodyShape` (`BodyShapeDbSchema`:
`Hourglass`, `Pear`, `Apple`, `Rectangle`, `InvertedTriangle`, `Oval`,
`Diamond`, `Athletic`), optional `bodyProportions` (JSON), optional
`clothingSizes` (JSON), optional `notes`, optional `measuredBy`
(`MeasuredBySchema`), `createdAt`, `updatedAt`.

### 8.6 `WardrobeItemDb` (`wardrobe.ts`)

The wardrobe item database schema. Note that colors are stored as hex strings
(for display) rather than LAB (for computation), since LAB conversion is applied
in the application layer when the item is loaded for recommendation processing.

`WardrobeItemDbSchema` — `id`, `profileId` (FK to `personal_profiles`),
`category` (`GarmentCategoryDbSchema`), optional `subcategory` (free-text),
optional `brand`, `name`, optional `description`, optional `primaryColorHex` /
`accentColorHex` (`HexColorSchema`), optional `fabricType`
(`FabricTypeDbSchema`), optional `fabricBlend` (free-text), optional `pattern`
(`PatternDbSchema`), optional `styleAesthetic` (`StyleAestheticDbSchema`),
`seasonSuitability` (string-array JSON, default `[]`), optional `formalityLevel`
(1–10), optional `condition` (`GarmentConditionDbSchema`), optional
`purchaseDate` / `purchasePrice` / `purchaseCurrency` / `retailer`, optional
`size` / `sizeRegion`, `wearCount` (default `0`), optional `lastWornDate`,
optional `careInstructions`, `imageUrls` (JSON array, default `[]`), `tags`
(JSON array, default `[]`), `isFavorite` (default `false`), `isArchived`
(default `false`), `createdAt`, `updatedAt`.

### 8.7 `Outfit` (`outfits.ts`)

`OutfitSchema` — `id`, `profileId` (FK), `name`, optional `description`, `items`
(string-array JSON of wardrobe item UUIDs, default `[]`), optional `occasion`,
optional `dressCode`, optional `weatherContext` (JSON), optional `dateWorn`,
optional `rating` (1–5), optional `notes`, optional `imageUrl`, `isPublic`
(default `false`), `createdAt`, `updatedAt`. The module also exports
`OutfitItemSchema`.

### 8.8 `RecommendationRecord` (`recommendations.ts`)

Recommendation records persist the full multi-dimensional confidence scores and
feedback, enabling the preference learning pipeline to train on both the
recommendation and its outcome.

`RecommendationRecordSchema` — `id`, `profileId` (FK), `type`
(`RecommendationTypeDbSchema`: `outfit` | `shopping` | `style_tip` |
`color_palette`), optional `context` (JSON), `items` (array of JSON objects,
default `[]`), seven optional 0–1 confidence scores (`confidenceScore`,
`styleMatchScore`, `colorHarmonyScore`, `occasionFitScore`,
`trendAlignmentScore`, `bodyFlatteryScore`, `weatherScore`), `stylingNotes`
(array of JSON objects, default `[]`), optional `wasAccepted`, optional
`feedbackRating` (1–5), optional `feedbackText`, `createdAt`.

### 8.9 `Product` (`products.ts`)

Products in the Aglaea database represent catalog items from retail partners,
normalized into the domain's type system for matching against profile
attributes.

`ProductSchema` — `id`, optional `externalId` (retailer sync key), `name`,
optional `brand`, optional `description`, optional `category` / `subcategory`,
optional `price` / `priceCurrency`, optional `retailerUrl`, `imageUrls` (JSON
array), `colors` (JSON array), optional `fabricType`, optional `pattern`,
optional `styleAesthetic`, `sizes` (JSON array), `seasonSuitability` (JSON
array), optional `formalityLevel` (1–10), optional `rating` (0–5), optional
`reviewCount`, `isAvailable` (default `true`), optional `lastSyncedAt`,
timestamps.

### 8.10 `Trend` (`trends.ts`)

`TrendSchema` plus its database-layer enums. Note that the database enums for
trend category, status, and origin use more granular values than the
`@aglaea/core` types, providing richer data for the trend forecasting engine.

- **`TrendCategoryDbSchema`** — `color`, `fabric`, `pattern`, `silhouette`,
  `style`, `accessory`, `footwear`, `beauty`, `lifestyle` (nine values, vs. six
  in core).
- **`TrendStatusDbSchema`** — `emerging`, `rising`, `peak`, `declining`,
  `fading`, `classic`, `revived` (seven values, vs. six in core).
- **`TrendOriginDbSchema`** — `runway`, `street`, `celebrity`, `social_media`,
  `subculture`, `designer`, `vintage_revival`, `technology`, `sustainability`
  (nine values, vs. six in core).

### 8.11 Other Schema Modules

These modules cover user preferences, feedback, reference data (ingredients,
fabrics, brands), social interactions, and audit logging.

- **`preferences.ts`** — `UserPreferencesSchema` plus
  `SustainabilityPreferenceSchema`, `SizePreferenceSchema`,
  `CoveragePreferenceSchema`, `FragranceConcentrationSchema`.
- **`feedback.ts`** — `FeedbackSchema`, `FeedbackTargetTypeSchema`.
- **`ingredients.ts`** — `IngredientSchema`, `IngredientCategoryDbSchema`.
- **`fabrics.ts`** — `FabricMaterialSchema`, `FabricCategoryDbSchema`,
  `PriceRangeDbSchema`.
- **`brands.ts`** — `BrandSchema` and `RetailerSchema`,
  `BrandPriceRangeDbSchema`.
- **`social.ts`** — `FollowSchema`, `ShareSchema`, `StyleBoardSchema`,
  `ShareTargetTypeSchema`, `SharePlatformSchema`.
- **`audit.ts`** — `AuditLogSchema`, `AuditActionSchema`.

---

## 9. SDK Surface (`@aglaea/sdk`)

`@aglaea/sdk` provides a high-level TypeScript client for consuming the
`@aglaea/api-services` endpoints. It handles authentication, retry logic, and
file uploads, so callers interact with typed domain methods rather than raw HTTP
requests.

`AglaeaClient` is the primary entry point, composed of five sub-components also
exported as instance fields:

- `http` (`AglaeaHttpClient`) — the underlying HTTP client
- `interceptors` (`InterceptorManager`) — request/response interceptor chain
- `retry` (`RetryHandler`) — exponential back-off with `maxRetries` defaulting
  to 3 and `baseDelayMs` to 1000
- `batch` (`BatchProcessor`) — batch multiple requests together
- `uploader` (`FileUploader`) — handles multipart file uploads for analysis and
  photo endpoints

The client is constructed from an `AglaeaSDKConfig`. Its domain methods wrap
REST endpoints through the retry handler:

| Method                                        | HTTP call                                   |
| --------------------------------------------- | ------------------------------------------- |
| `analyzeSkin(profileId, imageUrl)`            | `POST /api/v1/analysis/skin`                |
| `analyzeBody(profileId, measurements)`        | `POST /api/v1/analysis/body`                |
| `getColorPalette(profileId)`                  | `GET /api/v1/profiles/:profileId/colors`    |
| `getOutfitRecommendation(profileId, context)` | `POST /api/v1/recommendations/outfit`       |
| `getShoppingRecommendations(...)`             | `GET /api/v1/recommendations/shopping`      |
| `getTrends(category?, limit?)`                | `GET /api/v1/trends`                        |
| `getWardrobeItems(profileId)`                 | `GET /api/v1/profiles/:profileId/wardrobe`  |
| `addWardrobeItem(profileId, item)`            | `POST /api/v1/profiles/:profileId/wardrobe` |
| `uploadImage(profileId, file)`                | (file upload via `FileUploader`)            |
| `destroy()`                                   | Releases client resources                   |

The SDK module set also includes `auth`, `websocket`, and `http-client`. The SDK
uses its own client-facing result types (`APIResponse<T>`, `SkinAnalysisResult`,
`BodyAnalysisResult`, `ColorPalette`, `OutfitRecommendation`, `WardrobeItem`,
`TrendsResponse`, `ShoppingRecommendationsResponse`, `UploadResult`, etc.)
defined in `sdk/src/types.ts`.

---

## 10. Technology Stack

The technology choices below apply across all 93 Aglaea libraries. Each library
declares its own `package.json`, exposing source directly via
`"main": "./src/index.ts"` — there is no compiled output checked in, and
libraries depend on each other via TypeScript path aliases resolved at build
time.

| Layer            | Technology / Library                                             |
| ---------------- | ---------------------------------------------------------------- |
| Language         | TypeScript (Node.js, ESM — every package is `"type": "module"`)  |
| Validation       | Zod (every `@aglaea/core` and `@aglaea/database` schema)         |
| Persistence      | PostgreSQL — Knex for migrations, Zod schemas for row validation |
| Database extns.  | `uuid-ossp`, `pg_trgm`                                           |
| Dependency mgmt. | pnpm catalog (`catalog:` refs)                                   |
| Testing          | Vitest (`*.test.ts` / `*.spec.ts` co-located per library)        |
| Build executor   | `@nx/js:tsc`                                                     |
| Test executor    | `@nx/vite:test`                                                  |

Dependencies are kept minimal by design: `@aglaea/core` depends only on `zod`;
`@aglaea/database` on `knex`, `zod`, `uuid`. This keeps the foundation layer
importable without pulling in unnecessary transitive dependencies.

---

## Grounding

Every type, enum value, endpoint path, event constant, schema field, and
configuration default in this document was read directly from source under
`libs/aglaea/` — primarily `core/src/types.ts`, `core/src/constants.ts`,
`core/src/config.ts`, `ai-orchestrator/src/types.ts`, `events/src/types.ts`,
`events/src/events.ts`, `api-services/src/*.ts`, `database/src/schema/*.ts`,
`database/src/migrations/*.ts`, and `sdk/src/client.ts`. Counts (93 libraries,
64 endpoints, 23 event constants, 39 `EventDefinition` records, 16 schema
modules) reflect the current monorepo state. Aglaea is a pure library domain; no
`apps/aglaea/` or `services/aglaea/` exists.
