# Demeter — Technical Specifications

> Agriculture, home gardening, and environmental intelligence domain. Named
> after the Greek goddess of the harvest, agriculture, and the seasons.

---

This document is the authoritative technical reference for the Demeter domain.
It covers the data models and database schema defined in `@demeter/core`, the
public API surface of each of the 14 libraries, the Fastify REST API endpoints
and configuration, and the web and mobile application structures.

Demeter is fully implemented. The 14 libraries under `libs/demeter/` form the
domain logic layer — they are pure TypeScript modules with no runtime framework
dependencies beyond `@demeter/core`. The three applications under
`apps/demeter/` consume those libraries, with `@demeter/core` as the shared
schema and type contract between them. The REST API (`@demeter/api`) is the
primary integration point for external consumers.

---

## 1. Domain Overview

The table below provides a quick reference for the domain's scope and technology
choices.

| Property          | Value                                                             |
| ----------------- | ----------------------------------------------------------------- |
| Domain name       | `demeter`                                                         |
| Scope             | Home gardening, urban agriculture, controlled-environment growing |
| Library count     | 14 (`libs/demeter/*`)                                             |
| Application count | 3 (`apps/demeter/api`, `apps/demeter/web`, `apps/demeter/mobile`) |
| Database          | PostgreSQL via Drizzle ORM (schema defined in `@demeter/core`)    |
| Validation        | Zod — 30 schemas in `@demeter/core`                               |
| Language          | TypeScript (ESM)                                                  |
| API framework     | Fastify (`@demeter/api`)                                          |
| Web client        | React + Vite + React Router + TanStack Query + Zustand            |
| Mobile client     | React Native (Expo SDK 52) + React Navigation                     |

---

## 2. Library Inventory

All 14 libraries live under `libs/demeter/`, are private ESM packages at version
`0.1.0`, build with the `@nx/js:tsc` executor, and test with Vitest. Each
feature library declares a `peerDependency` on `@demeter/core`; `@demeter/core`
itself depends on `drizzle-orm` and `zod`.

The table below maps each package to its TypeScript source modules (these are
the files that actually export the library's functionality) and its high-level
responsibility. The `src/index.ts` re-export barrel is excluded from the module
list since it adds no new logic.

| Package                 | Source modules (`src/*.ts`, excluding `index.ts`)                                                                                                                    | Responsibility                                                                   |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `@demeter/core`         | `schemas`, `db-schema`, `db-seed`                                                                                                                                    | Zod schemas, Drizzle ORM tables and relations, seed data                         |
| `@demeter/plants`       | `species`, `companion`, `pests`, `climate`, `calendar`, `search`, `database`                                                                                         | Plant encyclopedia, companion planting, pest/disease data, climate zones         |
| `@demeter/weather`      | `data-sources`, `current-conditions`, `forecasting`, `alerts`, `historical`, `growing-degree-days`, `garden-weather`                                                 | Weather adapters, forecasting, GDD, garden alerts                                |
| `@demeter/sensors`      | `devices`, `protocols`, `readings`, `timeseries`, `calibration`, `integrations`                                                                                      | IoT device lifecycle, protocol adapters, timeseries analytics, calibration       |
| `@demeter/automation`   | `rules`, `irrigation`, `climate-control`, `alerts`, `scenes`, `history`                                                                                              | Rules engine, irrigation, greenhouse climate, alerts, scenes, history            |
| `@demeter/hydroponics`  | `systems`, `nutrients`, `reservoir`, `aquaponics`, `indoor-growing`, `microgreens`, `mushrooms`                                                                      | Hydroponics, aquaponics, indoor growing, microgreens, mushrooms                  |
| `@demeter/inventory`    | `seeds`, `suppliers`, `seed-saving`, `plant-inventory`, `supplies`                                                                                                   | Seed lots, suppliers, seed saving, plant inventory, supplies/tools               |
| `@demeter/planner`      | `layout`, `placement`, `sun-analysis`, `rotation`, `succession`, `templates`, `export`                                                                               | Layout engine, plant placement, sun analysis, rotation, succession, export       |
| `@demeter/journal`      | `observations`, `photos`, `growth-tracking`, `harvest-log`, `search`, `insights`                                                                                     | Observations, photos, growth tracking, harvest logging, insights                 |
| `@demeter/preservation` | `harvest-planning`, `canning`, `freezing`, `dehydrating`, `fermentation`, `storage`, `recipes`                                                                       | Harvest planning, canning, freezing, dehydrating, fermentation, storage, recipes |
| `@demeter/tasks`        | `scheduling`, `generation`, `prioritization`, `calendar-integration`, `notifications`, `analytics`                                                                   | Task scheduling, auto-generation, prioritization, iCal, notifications            |
| `@demeter/community`    | `profiles`, `exchange`, `groups`, `experts`, `content`, `local-resources`                                                                                            | Profiles, seed exchange, groups, expert network, content sharing, directory      |
| `@demeter/analytics`    | `yield-analytics`, `cost-analysis`, `resource-analytics`, `environmental-impact`, `time-analytics`, `success-metrics`, `dashboard`                                   | Yield, cost, resource, environmental, time, success metrics, dashboards          |
| `@demeter/intelligence` | `plant-identification`, `disease-detection`, `pest-identification`, `growth-prediction`, `recommendations`, `anomaly-detection`, `nlp-interface`, `model-management` | Plant ID, disease/pest detection, growth prediction, recommendations, NLP        |

`@demeter/planner` is the only library with a second peer dependency: it peers
on both `@demeter/core` and `@demeter/plants`. All other feature libraries peer
only on `@demeter/core`.

---

## 3. Data Models — `@demeter/core` (`schemas.ts`)

`@demeter/core/schemas.ts` is the authoritative data contract for the entire
domain. It defines 30 Zod schemas organized into three tiers: enumerations
(closed value sets), value objects (composite but non-entity types), and
entities (the top-level domain objects that map to database rows). Every schema
carries `.describe()` metadata, and a TypeScript type is inferred from each
schema with `z.infer`. Schemas use shared field helpers: `id` / `userId`
(`z.string().uuid()`), `datetime` (`z.string().datetime()`), and `dateOnly`
(`YYYY-MM-DD` regex).

The three tiers are described below. Enumerations are listed concisely as a
table; value objects and entities include per-field details because their
validation constraints are significant for implementors.

### 3.1 Enumerations (10)

| Schema                 | Values                                                                                                      |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `PlantTypeSchema`      | `vegetable`, `fruit`, `herb`, `flower`, `tree`, `shrub`, `vine`, `grass`, `succulent`, `fern`               |
| `PlantLifecycleSchema` | `annual`, `biennial`, `perennial`                                                                           |
| `GrowthStageSchema`    | `seed`, `germination`, `seedling`, `vegetative`, `flowering`, `fruiting`, `harvest`, `dormant`              |
| `SoilTypeSchema`       | `clay`, `sandy`, `loamy`, `silty`, `peaty`, `chalky`                                                        |
| `TaskTypeSchema`       | `water`, `fertilize`, `prune`, `harvest`, `weed`, `pest_control`, `plant`, `transplant`, `mulch`, `compost` |
| `SensorTypeSchema`     | `moisture`, `temperature`, `humidity`, `light`, `ph`, `ec`, `flow`, `wind`, `rain`                          |
| `IrrigationTypeSchema` | `drip`, `sprinkler`, `soaker`, `manual`, `flood`, `mist`                                                    |
| `GardenTypeSchema`     | `in_ground`, `raised_bed`, `container`, `vertical`, `greenhouse`, `indoor`, `hydroponic`, `aquaponic`       |
| `PestTypeSchema`       | `insect`, `mammal`, `bird`, `fungal`, `bacterial`, `viral`, `nematode`                                      |
| `TreatmentTypeSchema`  | `organic`, `chemical`, `biological`, `mechanical`, `cultural`, `integrated`                                 |

### 3.2 Value Objects (10)

**`ClimateZoneSchema`** — USDA hardiness zone and climate characteristics.

| Field            | Type / constraint           | Required     |
| ---------------- | --------------------------- | ------------ | --- |
| `usdaZone`       | string, regex `^(1[0-3][ab] | [1-9][ab])$` | yes |
| `koppenCode`     | string, length 2–4          | yes          |
| `minTempF`       | number, −80 to 80           | yes          |
| `maxTempF`       | number, 30 to 130           | yes          |
| `frostFreeDays`  | integer, 0 to 365           | yes          |
| `lastFrostDate`  | `dateOnly` (YYYY-MM-DD)     | yes          |
| `firstFrostDate` | `dateOnly`                  | yes          |

**`GeoLocationSchema`** — `latitude` (−90..90), `longitude` (−180..180),
`elevation` (meters, −500..9000), `aspect` (enum `N|S|E|W|NE|NW|SE|SW`).

**`SoilCompositionSchema`** — `type` (`SoilTypeSchema`), `ph` (0..14),
`nitrogenPpm` (0..500), `phosphorusPpm` (0..500), `potassiumPpm` (0..1000),
`organicMatterPercent` (0..100), `cec` (Cation Exchange Capacity, 0..100
meq/100g), `texture` (string, 1..100).

**`SunExposureSchema`** — `category`
(`full_sun|partial_sun|partial_shade|full_shade`), `dailyHours` (0..24),
`morningHours` (0..12), `afternoonHours` (0..12).

**`WaterRequirementSchema`** — `frequencyDays` (1..90), `amountLiters`
(0.01..1000), `method` (`IrrigationTypeSchema`), `seasonalAdjustments` (object
of `spring|summer|fall|winter` multipliers, each 0..5).

**`TemperatureRangeSchema`** — `minCelsius`, `maxCelsius`, `optimalMinCelsius`,
`optimalMaxCelsius`, each −50..60.

**`GrowingConditionsSchema`** — composite: `soil` (`SoilCompositionSchema`),
`sun` (`SunExposureSchema`), `water` (`WaterRequirementSchema`),
`temperatureRange` (`TemperatureRangeSchema`), `humidityRange` (`min`/`max`,
0..100).

**`PlantSpacingSchema`** — `rowSpacingCm` (1..1000), `plantSpacingCm` (1..1000),
`depthCm` (0.1..100), `thinToSpacingCm` (1..1000, optional).

**`HarvestWindowSchema`** — `startDaysFromPlanting`, `endDaysFromPlanting`,
`peakDaysFromPlanting` (each integer 1..3650), `signsOfReadiness` (non-empty
array of strings, each 1..500 chars).

**`NutrientProfileSchema`** — fertilizer N-P-K profile: `nitrogenRatio`,
`phosphorusRatio` (0..100), `potassiumRatio` (0..100),
`applicationRateGramsPerSqM` (0..5000), `frequency` (string 1..200), `notes`
(string ≤2000, default `''`).

### 3.3 Entities (10)

**`GardenSchema`**

| Field                     | Type / constraint   | Required     |
| ------------------------- | ------------------- | ------------ |
| `id`                      | UUID                | yes          |
| `userId`                  | UUID                | yes          |
| `name`                    | string, 1–200       | yes          |
| `description`             | string, ≤5000       | default `''` |
| `location`                | `GeoLocationSchema` | yes          |
| `climateZone`             | `ClimateZoneSchema` | yes          |
| `areaSqMeters`            | number, 0.1–100000  | yes          |
| `type`                    | `GardenTypeSchema`  | yes          |
| `beds`                    | array of UUID       | default `[]` |
| `createdAt` / `updatedAt` | `datetime`          | yes          |

**`GardenBedSchema`** — `id`, `gardenId` (UUID), `name` (1–200), `widthCm`
(1–10000), `lengthCm` (1–10000), `heightCm` (0–500, default 0),
`soilComposition` (`SoilCompositionSchema`), `sunExposure`
(`SunExposureSchema`), `irrigationType` (`IrrigationTypeSchema`), `plantings`
(array of UUID, default `[]`), `notes` (≤5000, default `''`), `createdAt`,
`updatedAt`.

**`PlantSchema`** — a plant species/variety in the database. Fields: `id`,
`commonName` (1–200), `botanicalName` (1–300), `family`, `genus`, `species`
(each 1–200), `variety` (1–200, optional), `type` (`PlantTypeSchema`),
`lifecycle` (`PlantLifecycleSchema`), `growingConditions`
(`GrowingConditionsSchema`), `spacing` (`PlantSpacingSchema`),
`daysToGermination` (`{ min, max }` integers 1–365), `daysToMaturity`
(`{ min, max }` integers 1–3650), `harvestWindow` (`HarvestWindowSchema`),
`companionPlantIds` (array of UUID, default `[]`), `incompatiblePlantIds` (array
of UUID, default `[]`), `tags` (array of string, default `[]`).

**`PlantingSchema`** — a planting instance in a bed. Fields: `id`, `gardenBedId`
(UUID), `plantId` (UUID), `userId` (UUID), `quantity` (integer 1–10000),
`plantedDate` (`dateOnly`), `expectedHarvestDate` (`dateOnly`, optional),
`actualHarvestDate` (`dateOnly`, optional), `currentStage`
(`GrowthStageSchema`), `healthRating` (integer 1–10), `notes` (≤5000, default
`''`), `spacingUsedCm` (1–1000), `rowNumber` (integer 1–100, optional),
`createdAt`, `updatedAt`.

**`HarvestSchema`** — `id`, `plantingId` (UUID), `userId` (UUID), `harvestDate`
(`dateOnly`), `quantityKg` (0.001–100000), `qualityRating` (integer 1–5),
`notes` (≤5000, default `''`), `usedFor` (enum
`fresh|preserved|shared|composted`), `createdAt`.

**`TaskSchema`** — `id`, `gardenId` (UUID), `userId` (UUID), `type`
(`TaskTypeSchema`), `title` (1–500), `description` (≤5000, default `''`),
`dueDate` (`dateOnly`), `completedDate` (`dateOnly`, optional), `isRecurring`
(boolean, default false), `recurrenceRule` (string ≤200, optional — cron-like),
`priority` (enum `low|medium|high|urgent`), `plantingIds` (array of UUID,
default `[]`), `notes` (≤5000, default `''`), `createdAt`, `updatedAt`.

**`ObservationSchema`** — `id`, `gardenId` (UUID), `userId` (UUID), `plantingId`
(UUID, optional), `date` (`dateOnly`), `title` (1–500), `notes` (1–10000),
`photoUrls` (array of URL strings ≤2000 chars, default `[]`), `metrics` (object
with optional `temperature` −50..60, `humidity` 0..100, `soilMoisture` 0..100,
`rainfall` 0..500; default `{}`), `tags` (array of string, default `[]`),
`createdAt`.

**`SensorSchema`** — a physical sensor device. Fields: `id`, `gardenId` (UUID),
`gardenBedId` (UUID, optional), `type` (`SensorTypeSchema`), `name` (1–200),
`manufacturer` (1–200, optional), `model` (1–200, optional), `serialNumber`
(1–200, optional), `calibratedAt` (`datetime`, optional), `batteryLevel`
(integer 0–100, optional), `isActive` (boolean, default true),
`locationDescription` (1–500), `createdAt`, `updatedAt`.

**`AlertSchema`** — `id`, `userId` (UUID), `gardenId` (UUID, optional), `type`
(enum
`frost_warning|pest_alert|water_reminder|harvest_ready|task_due|sensor_alert|weather_alert`),
`severity` (enum `info|warning|critical`), `title` (1–500), `message` (1–5000),
`relatedEntityId` (UUID, optional), `relatedEntityType` (string 1–100,
optional), `isRead` (boolean, default false), `readAt` (`datetime`, optional),
`createdAt`.

**`UserSchema`** — a gardener profile. Fields: `id`, `email` (email ≤255),
`displayName` (1–255), `location` (`GeoLocationSchema`, optional), `climateZone`
(`ClimateZoneSchema`, optional), `experienceLevel` (enum
`beginner|intermediate|advanced|expert`), `preferredUnits` (enum
`metric|imperial`, default `metric`), `gardenIds` (array of UUID, default `[]`),
`createdAt`, `updatedAt`.

---

## 4. Database Schema — `@demeter/core` (`db-schema.ts`)

The Drizzle ORM schema in `db-schema.ts` is the concrete relational
representation of the Zod entity models from §3. Every Zod entity in §3.3 maps
to a `demeter_`-prefixed PostgreSQL table here; composite Zod value objects
(soil composition, sun exposure, climate zone) are stored as `jsonb` columns
rather than normalized into additional tables, keeping queries simple without
sacrificing type safety.

The schema defines 20 pg enums, 17 tables (all with the `demeter_` prefix), and
the relations between them. Convenience arrays `ALL_DEMETER_TABLES`,
`ALL_DEMETER_RELATIONS`, and `ALL_DEMETER_ENUMS` are exported for introspection.

### 4.1 PostgreSQL Enums (20)

`demeter_plant_type`, `demeter_plant_lifecycle`, `demeter_growth_stage`,
`demeter_soil_type`, `demeter_task_type`, `demeter_sensor_type`,
`demeter_irrigation_type`, `demeter_garden_type`, `demeter_pest_type`,
`demeter_treatment_type`, `demeter_task_priority`, `demeter_alert_type`,
`demeter_alert_severity`, `demeter_experience_level`, `demeter_unit_system`,
`demeter_sun_exposure_category`, `demeter_harvest_usage`, `demeter_aspect`,
`demeter_pest_severity` (`minor|moderate|severe|critical`),
`demeter_treatment_effectiveness`
(`ineffective|slightly_effective|moderately_effective|very_effective|completely_effective`).

### 4.2 Tables (17)

| #   | Table                      | Key columns and foreign keys                                                                                                                                                                                                                               |
| --- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | `demeter_users`            | `email` (unique), `display_name`, location columns, `climate_zone` (jsonb), `experience_level`, `preferred_units`, soft-delete `deleted_at`                                                                                                                |
| 2   | `demeter_gardens`          | `user_id` → users (cascade), `name`, `description`, location columns, `climate_zone` (jsonb), `area_sq_meters`, `type`, `deleted_at`                                                                                                                       |
| 3   | `demeter_garden_beds`      | `garden_id` → gardens (cascade), `width_cm`, `length_cm`, `height_cm`, `soil_composition` (jsonb), `sun_exposure` (jsonb), `irrigation_type`, `deleted_at`                                                                                                 |
| 4   | `demeter_plants`           | `common_name`, `botanical_name`, `family`, `genus`, `species`, `variety`, `type`, `lifecycle`, `growing_conditions` (jsonb), `spacing` (jsonb), `days_to_germination` / `days_to_maturity` / `harvest_window` (jsonb), `tags` (jsonb)                      |
| 5   | `demeter_companion_plants` | `plant_id` → plants, `companion_plant_id` → plants (both cascade), `is_compatible`, `reason`; unique on the pair                                                                                                                                           |
| 6   | `demeter_plantings`        | `garden_bed_id` → beds (cascade), `plant_id` → plants (restrict), `user_id` → users (cascade), `quantity`, `planted_date`, `expected_harvest_date`, `actual_harvest_date`, `current_stage`, `health_rating`, `spacing_used_cm`, `row_number`, `deleted_at` |
| 7   | `demeter_planting_stages`  | `planting_id` → plantings (cascade), `stage`, `entered_at`, `exited_at`, `health_rating_at_entry`                                                                                                                                                          |
| 8   | `demeter_harvests`         | `planting_id` → plantings (cascade), `user_id` → users (cascade), `harvest_date`, `quantity_kg`, `quality_rating`, `used_for`                                                                                                                              |
| 9   | `demeter_tasks`            | `garden_id` → gardens (cascade), `user_id` → users (cascade), `type`, `title`, `due_date`, `completed_date`, `is_recurring`, `recurrence_rule`, `priority`, `planting_ids` (jsonb), `deleted_at`                                                           |
| 10  | `demeter_observations`     | `garden_id` → gardens (cascade), `user_id` → users (cascade), `planting_id` → plantings (set null), `date`, `title`, `notes`, `photo_urls` (jsonb), `metrics` (jsonb), `tags` (jsonb)                                                                      |
| 11  | `demeter_sensors`          | `garden_id` → gardens (cascade), `garden_bed_id` → beds (set null), `type`, `name`, `manufacturer`, `model`, `serial_number`, `calibrated_at`, `battery_level`, `is_active`, `location_description`, `deleted_at`                                          |
| 12  | `demeter_sensor_readings`  | time-series: `sensor_id` → sensors (cascade), `value`, `unit`, `recorded_at`, `metadata` (jsonb); composite index on `(sensor_id, recorded_at)`                                                                                                            |
| 13  | `demeter_alerts`           | `user_id` → users (cascade), `garden_id` → gardens (set null), `type`, `severity`, `title`, `message`, `related_entity_id`, `related_entity_type`, `is_read`, `read_at`                                                                                    |
| 14  | `demeter_pest_reports`     | `garden_id` → gardens (cascade), `user_id` → users (cascade), `planting_id` → plantings (set null), `pest_type`, `pest_name`, `severity`, `description`, `photo_urls` (jsonb), `affected_area`, `first_observed_date`, `resolved_date`                     |
| 15  | `demeter_treatments`       | `pest_report_id` → pest_reports (cascade), `user_id` → users (cascade), `treatment_type`, `product_name`, `description`, `application_date`, `reapplication_date`, `effectiveness`                                                                         |
| 16  | `demeter_weather_records`  | `garden_id` → gardens (cascade), `date`, `high/low/avg_temp_celsius`, `humidity`, `rainfall_mm`, `wind_speed_kmh`, `wind_direction`, `uv_index`, `conditions`, `frost_occurred`; composite index on `(garden_id, date)`                                    |
| 17  | `demeter_soil_tests`       | `garden_bed_id` → beds (cascade), `user_id` → users (cascade), `test_date`, `ph`, `nitrogen/phosphorus/potassium_ppm`, `organic_matter_percent`, `cec`, `soil_type`, `texture`, `lab_name`, `recommendations`                                              |

All tables use `uuid` primary keys with `defaultRandom()` and timezone-aware
`created_at` / `updated_at` timestamps. Tables with a `deleted_at` column
support soft deletion. Every foreign key and frequently queried column has an
explicit index.

### 4.3 Relations

`db-schema.ts` defines Drizzle `relations()` for all 17 tables. Notable
many-to-one and one-to-many links: a user owns many gardens, plantings,
harvests, tasks, observations, alerts, pest reports, treatments, and soil tests;
a garden has many beds, tasks, observations, sensors, alerts, pest reports, and
weather records; a planting has many stages, harvests, observations, and pest
reports; a pest report has many treatments. The `demeter_companion_plants`
junction uses named relations (`sourcePlant`, `companionPlant`) for its
self-referencing link to `demeter_plants`.

### 4.4 Seed Data — `@demeter/core` (`db-seed.ts`)

`db-seed.ts` exports typed seed datasets with deterministic UUIDs:

- `SEED_USERS` — 3 sample users (beginner / intermediate / expert) with USDA
  zones 7b, 9a, 6a and real coordinates.
- `SEED_GARDENS` — 4 gardens (in-ground, container, raised-bed, greenhouse).
- `SEED_GARDEN_BEDS` — 8 beds with full soil composition and sun exposure.
- `SEED_PLANTS` — 32 garden plants with real botanical data (taxonomy, spacing,
  germination/maturity ranges, harvest windows, tags).
- `SEED_PLANTINGS` — 15 plantings tied to the seed beds and plants.
- `SEED_TASKS` — 7 tasks, several with cron recurrence rules.
- `SEED_OBSERVATIONS` — 4 observations with environmental metrics.
- `SEED_HARVESTS` — 6 harvest records.

---

## 5. Library API Surface

The 14 libraries are pure TypeScript modules with no framework or runtime
dependencies beyond `@demeter/core`. Each library's public surface falls into
four categories:

- **Factory functions** — create domain objects (e.g.,
  `createSystemFromTemplate`, `createCanningBatch`)
- **Data constants** — uppercase `*_DATABASE` / `*_RECIPES` / `*_GUIDES` exports
  that ship pre-populated reference data (plant encyclopedias, substrate
  recipes, USDA altitude tables, etc.)
- **Pure calculation functions** — deterministic computations over domain inputs
  (GDD accumulation, EC↔TDS conversion, DLI calculation, haversine distance)
- **Inferred types** — TypeScript types derived from `@demeter/core` Zod schemas

The community library additionally exports in-memory store helpers and
`reset*Stores()` functions for test isolation. The selected exports below cover
the most complex libraries; the remaining libraries (`weather`, `sensors`,
`automation`, `journal`, `tasks`, `analytics`, `intelligence`) re-export their
entire module surface via `export *` in their `index.ts` — their module files
are listed in §2.

### `@demeter/plants`

- **Species** — `createPlantSpecies`, `getSpeciesById`, `searchSpeciesByName`,
  `getSpeciesByType`, `getSpeciesByFamily`.
- **Companion** — `COMPANION_DATABASE`; `getCompanions`,
  `getBeneficialCompanions`, `getDetrimentalCompanions`,
  `scoreCompanionPairing`, `suggestGuildPlanting`, `generateThreeSistersLayout`,
  `checkBedCompatibility`, `generateCompanionChart`.
- **Pests** — `PEST_DATABASE`, `DISEASE_DATABASE`, `BENEFICIAL_INSECTS`;
  `identifyPest`, `recommendTreatments`, `generateIPMPlan`.
- **Climate** — `USDA_ZONES`, `KOPPEN_CLASSIFICATIONS`, `AHS_HEAT_ZONES`,
  `FROST_DATES_BY_STATE`; `getUSDAZone`, `getKoppenClassification`,
  `getAHSHeatZone`, `calculateGrowingSeasonLength`,
  `calculateGrowingDegreeDays`, `calculateChillHours`, `isPlantHardy`,
  `filterPlantsByZone`, `estimateMicroclimate`.
- **Calendar** — `PLANTING_CALENDAR`; `getPlantingCalendar`, `getMonthlyTasks`,
  `createSeedStartSchedule`, `createTransplantSchedule`,
  `createSuccessionSchedule`, `createFallPlantingCalendar`,
  `calculateMoonPhase`, `getMoonPlantingAdvice`, `generatePrintableCalendar`.
- **Search** — `searchPlants`, `fuzzyNameSearch`, `facetedSearch`,
  `findSimilarPlants`, `suggestForBeginners`, `suggestProblemSolvers`,
  `suggestByHarvestTime`, `suggestPollinatorPlants`, `suggestEdibleLandscaping`,
  `searchByNutrition`.
- **Database** — `VEGETABLE_DATABASE`, `HERB_DATABASE`, `FRUIT_DATABASE`,
  `FLOWER_DATABASE`; `getAllPlants`, `getPlantsByCategory`, `getPlantCount`,
  `validateDatabase`.

### `@demeter/hydroponics`

- **Systems** — seven `HydroponicSystemType` configs (`NFTConfig`, `DWCConfig`,
  `EbbFlowConfig`, `DripConfig`, `AeroponicConfig`, `KratkyConfig`,
  `WickConfig`); `SYSTEM_TEMPLATES`, `SETUP_GUIDES`; `createSystemFromTemplate`,
  `calculateSetupCost`, `estimateDailyPowerWatts`,
  `estimateMonthlyElectricityCost`, `validateSystemConfig`,
  `getRecommendedCrops`, `compareSystems`.
- **Nutrients** — `EC_RANGES`, `NUTRIENT_LOCKOUTS`, `DEFICIENCY_SYMPTOMS`,
  `NUTRIENT_BRANDS`, `PLANT_NUTRIENT_PROFILES`; `calculateECCorrection`,
  `ecToTDS`, `tdsToEC`, `calculatePHAdjustment`, `checkNutrientLockout`,
  `diagnoseDeficiency`, `calculateMixingRecipe`.
- **Reservoir** — `getWaterLevelStatus`, `getTemperatureControlState`,
  `generateDosingCommand`, `calculateEvaporationRate`, `detectAnomalies`,
  `estimateDaysUntilRefill`.
- **Aquaponics** — `FISH_SPECIES_DATABASE`, `FISH_DISEASE_DATABASE`;
  `calculateMaxFishCount`, `calculateFeedingAmount`, `assessCyclingStatus`,
  `calculateFishToPlantRatio`, `calculateFeedConversionRatio`,
  `diagnoseFishDisease`, `generateBalanceReport`.
- **Indoor growing** — `GROW_LIGHT_DATABASE`, `VPD_RANGES`, `DLI_TARGETS`;
  `calculateVPD`, `calculateDLI`, `calculatePPFDForDLI`, `getPPFDAtDistance`,
  `getPhotoperiodSchedule`, `calculateCO2Supplementation`,
  `calculateElectricityCost`, `generateVPDChart`.
- **Microgreens** — `MICROGREEN_VARIETIES`; `calculateSeedDensity`,
  `createTray`, `harvestTray`, `generateProductionSchedule`,
  `getMostProfitableVarieties`.
- **Mushrooms** — `MUSHROOM_SPECIES_DATABASE`, `CONTAMINATION_DATABASE`,
  `SUBSTRATE_RECIPES`; `calculateBiologicalEfficiency`, `createMushroomGrow`,
  `recordFlush`, `identifyContamination`, `calculateSpawnAmount`,
  `validateFruitingConditions`.

### `@demeter/planner`

- **Layout** — `UndoRedoHistory` class; `createCanvas`, `addElement`,
  `removeElement`, `moveElement`, `rotateElement`, `resizeElement`,
  `snapToGrid`, `copyElement`, `calculateTotalArea`, `calculateBedArea`.
- **Placement** — `createSquareFootGrid`, `assignPlantToCell`,
  `calculatePlantsPerSquareFoot`, `createRowPlanting`, `validateSpacing`,
  `checkCompanionCompatibility`, `calculateMatureSizeOverlap`,
  `planVerticalSpace`.
- **Sun analysis** — `calculateSunPosition`, `calculateSunrise`,
  `calculateSunset`, `calculateDaylength`, `calculateShadow`,
  `calculateStructureShadows`, `calculateTreeShadows`,
  `calculateDailyLightIntegral`, `generateSunHeatmap`,
  `findOptimalBedPlacement`.
- **Rotation** — `ROTATION_GROUPS`; `generateRotationPlan`,
  `validate4YearRotation`, `suggestNextCrop`, `trackNitrogenBalance`,
  `planCoverCrops`, `accountForPerennials`.
- **Succession** — `calculateSowingIntervals`, `createSuccessionPlan`,
  `calculateSeedQuantity`, `generateHarvestTimeline`, `trackActualVsPlanned`.
- **Templates** — `TEMPLATE_LIBRARY`; `instantiateTemplate`,
  `createCustomTemplate`.
- **Export** — `generatePlantShoppingList`, `generateSeedOrderList`,
  `generateMaterialsList`, `generatePlantingCalendarFromPlan`,
  `generateBedLabels`, `exportPlanAsJSON`, `importPlanFromJSON`,
  `generateTaskListFromPlan`.

### `@demeter/preservation`

- **Canning** — `CANNING_RECIPES`, `WATER_BATH_ALTITUDE_ADJUSTMENTS`,
  `PRESSURE_ALTITUDE_DIAL`, `PRESSURE_ALTITUDE_WEIGHTED`;
  `getAdjustedProcessingTime`, `getAdjustedPressureDial`,
  `determineCanningMethod`, `calculateAcidAddition`, `createCanningBatch`,
  `checkBatchSeals`, `performSafetyCheck`, `isWaterBathSafe`, `getBotulismRisk`.
- **Freezing** — `FREEZING_GUIDES`, `STORAGE_DURATION_DATABASE`;
  `createFreezerItem`, `calculateFreezerSpace`, `sortByFIFO`,
  `generateExpirationAlerts`, `getFIFOPriority`.
- **Dehydrating** — `DEHYDRATING_GUIDES`, `FRUIT_LEATHER_RECIPES`,
  `JERKY_RECIPES`, `HERB_DRYING_GUIDES`; `calculateReconstitution`,
  `calculateDriedWeight`, `getShelfLife`, `assessDryingQuality`.
- **Fermentation** — `FERMENTATION_RECIPES`, `LACTO_MILESTONES`,
  `KOMBUCHA_MILESTONES`; `createFermentBatch`, `recordPH`,
  `calculateSaltForBrine`, `createKombuchaBrew`, `assessFermentSafety`.
- **Storage** — `STORAGE_CONDITIONS`, `SPOILAGE_INDICATORS`;
  `checkConditionCompliance`, `generateStorageAlerts`,
  `checkStorageCompatibility`, `findBestLocation`.
- **Harvest planning** — `CROP_YIELD_DATABASE`; `estimateYield`,
  `createHarvestForecast`, `predictPeakHarvest`, `schedulePreservationSessions`,
  `calculateContainerNeeds`.
- **Recipes** — `PRESERVATION_RECIPES`; `matchHarvestToRecipes`,
  `parseRecipeFromText`, `calculateSuccessRate`.

---

## 6. Application — `@demeter/api`

`apps/demeter/api` is a Fastify REST API that exposes the full Demeter domain
over HTTP. It is the primary integration point for the web and mobile apps and
for IoT devices. Project type `application`, tags `scope:demeter`, `type:app`,
`layer:service`. Runtime dependencies: `@demeter/core`, `@oshun/errors`,
`@oshun/logging`, `fastify`, `drizzle-orm`, `pg`, `ioredis`, `zod`, `bcryptjs`,
`nanoid`, and the `@fastify/*` plugins.

### 6.1 Server Composition (`app.ts`)

Plugin registration order matters in Fastify because later plugins can access
decorations added by earlier ones. `buildServer()` registers plugins in this
fixed order:

1. **request-context** — attaches per-request context for tracing and logging
2. **CORS** — cross-origin resource sharing headers
3. **error-handler** — normalizes all errors to the standard error envelope
4. **Swagger/OpenAPI** — auto-generated API documentation
5. **auth (JWT)** — JWT verification, decorates requests with user identity
6. **database (PostgreSQL + Drizzle)** — connection pool and ORM instance
7. **Redis** — cache, rate-limit backing store, and session store
8. **rate-limit** — per-IP and per-user request throttling
9. **routes** — all `/v1/*` route handlers

The Fastify instance uses Pino logging (`pino-pretty` in development),
`trustProxy: true`, and registers SIGINT/SIGTERM handlers for graceful shutdown.
`server.ts` owns process startup.

### 6.2 Plugins (`src/plugins/`)

`request-context`, `cors`, `error-handler`, `swagger`, `auth`, `database`,
`rate-limit`, `redis`. The database plugin creates a `pg.Pool`
(`connectionTimeoutMillis: 5000`, `idleTimeoutMillis: 30000`) and decorates the
instance with `fastify.db` (Drizzle, configured with the `@demeter/core` schema)
and `fastify.dbPool` (raw pool, drained on `onClose`). The redis plugin
decorates `fastify.redis` (ioredis) and `fastify.cache` (a typed `CacheHelper`
with JSON serialization and TTL).

### 6.3 Middleware (`src/middleware/`)

- **RBAC (`rbac.ts`)** — five roles: `owner` (4) > `admin` (3) > `member` (2) >
  `viewer` (1), plus `iot_device` (0, no hierarchy). 12 permission strings
  (`garden:read|write|delete`, `plant:read|write`, `sensor:read|write`,
  `task:read|write`, `harvest:read|write`, `admin:manage`). `requireRole(roles)`
  and `requirePermission(permissions)` are Fastify preHandler factories;
  `requirePermission` requires ALL listed permissions.
- **Ownership (`ownership.ts`)** — `requireOwnership(resourceType)` resolves a
  resource owner from registered in-memory stores (direct `userId` field, or
  indirect via the owning garden). Resource types: `planting`, `task`,
  `harvest`, `sensor`, `observation`. `admin` and `owner` roles bypass the
  check.
- **Session (`session.ts`)** — session-management helpers.

### 6.4 Authentication (`routes/auth.ts`)

Mounted at `/v1/auth`. Capabilities: email/password registration and login
(bcrypt password hashing), refresh-token rotation with family-based theft
detection, OAuth2 token exchange for Google / Apple / Facebook, magic-link
(passwordless) login, API-key issuance for IoT devices, and session management.
Raw refresh tokens, API keys, and magic-link tokens are never stored — only
hashes.

| Method | Path                         | Purpose                               | Auth        |
| ------ | ---------------------------- | ------------------------------------- | ----------- |
| POST   | `/v1/auth/register`          | Register a new account                | none        |
| POST   | `/v1/auth/login`             | Login with email and password         | none        |
| POST   | `/v1/auth/logout`            | Logout / invalidate session           | bearer      |
| POST   | `/v1/auth/refresh`           | Refresh access token (token rotation) | refresh tok |
| POST   | `/v1/auth/oauth/google`      | Login with Google                     | id token    |
| POST   | `/v1/auth/oauth/apple`       | Login with Apple                      | id token    |
| POST   | `/v1/auth/oauth/facebook`    | Login with Facebook                   | access tok  |
| POST   | `/v1/auth/magic-link`        | Request a magic link                  | none        |
| POST   | `/v1/auth/magic-link/verify` | Verify a magic link                   | magic token |
| POST   | `/v1/auth/api-keys`          | Create an API key                     | bearer      |
| GET    | `/v1/auth/api-keys`          | List API keys                         | bearer      |
| DELETE | `/v1/auth/api-keys/:id`      | Revoke an API key                     | bearer      |
| GET    | `/v1/auth/sessions`          | List active sessions                  | bearer      |
| DELETE | `/v1/auth/sessions/:id`      | Revoke a session                      | bearer      |
| DELETE | `/v1/auth/sessions`          | Revoke all sessions                   | bearer      |

### 6.5 REST Endpoints

All routes below require a bearer JWT unless noted. `:id` parameters are path
parameters. The route files use in-memory stores for development; the database
plugin provides the Drizzle/PostgreSQL backing for production use.

**Health** (`routes/health.ts`, no auth, no version prefix)

| Method | Path              | Purpose         |
| ------ | ----------------- | --------------- |
| GET    | `/health`         | Liveness probe  |
| GET    | `/health/ready`   | Readiness probe |
| GET    | `/health/startup` | Startup probe   |

**Gardens** (`/v1/gardens`)

| Method | Path                            | Purpose                  |
| ------ | ------------------------------- | ------------------------ |
| GET    | `/v1/gardens`                   | List gardens (paginated) |
| POST   | `/v1/gardens`                   | Create a garden          |
| GET    | `/v1/gardens/:id`               | Get garden by ID         |
| PUT    | `/v1/gardens/:id`               | Update a garden          |
| DELETE | `/v1/gardens/:id`               | Soft-delete a garden     |
| POST   | `/v1/gardens/:id/beds`          | Create a garden bed      |
| GET    | `/v1/gardens/:id/beds`          | List garden beds         |
| PUT    | `/v1/gardens/:id/beds/:bedId`   | Update a garden bed      |
| DELETE | `/v1/gardens/:id/beds/:bedId`   | Delete a garden bed      |
| POST   | `/v1/gardens/:id/share`         | Share a garden           |
| DELETE | `/v1/gardens/:id/share/:userId` | Revoke garden sharing    |
| GET    | `/v1/gardens/:id/members`       | List garden members      |

**Plants** (`/v1/plants`)

| Method | Path                        | Purpose                         |
| ------ | --------------------------- | ------------------------------- |
| GET    | `/v1/plants`                | List plants                     |
| GET    | `/v1/plants/search`         | Search plants                   |
| GET    | `/v1/plants/:id`            | Get plant by ID (Redis-cached)  |
| GET    | `/v1/plants/:id/care`       | Get care instructions           |
| GET    | `/v1/plants/:id/companions` | Get companion planting info     |
| GET    | `/v1/plants/:id/pests`      | Get common pests for a plant    |
| GET    | `/v1/plants/:id/diseases`   | Get common diseases for a plant |
| GET    | `/v1/plants/recommend`      | Get plant recommendations       |
| GET    | `/v1/plants/faceted`        | Faceted plant search            |
| GET    | `/v1/plants/pests`          | Search the pest database        |
| GET    | `/v1/plants/diseases`       | Search the disease database     |

**Plantings** (`/v1/plantings`)

| Method | Path                      | Purpose             |
| ------ | ------------------------- | ------------------- |
| GET    | `/v1/plantings`           | List plantings      |
| POST   | `/v1/plantings`           | Create a planting   |
| GET    | `/v1/plantings/:id`       | Get planting by ID  |
| PUT    | `/v1/plantings/:id`       | Update a planting   |
| PATCH  | `/v1/plantings/:id/stage` | Update growth stage |

**Tasks** (`/v1/tasks`)

| Method | Path                     | Purpose                   |
| ------ | ------------------------ | ------------------------- |
| GET    | `/v1/tasks`              | List garden tasks         |
| POST   | `/v1/tasks`              | Create a garden task      |
| PATCH  | `/v1/tasks/:id/complete` | Complete a task           |
| DELETE | `/v1/tasks/:id`          | Delete a task             |
| POST   | `/v1/tasks/generate`     | Generate task suggestions |
| GET    | `/v1/tasks/calendar`     | Calendar view of tasks    |
| POST   | `/v1/tasks/:id/snooze`   | Snooze a task             |
| GET    | `/v1/tasks/overdue`      | List overdue tasks        |

**Harvests** (`/v1/harvests`)

| Method | Path                   | Purpose                   |
| ------ | ---------------------- | ------------------------- |
| GET    | `/v1/harvests`         | List harvests             |
| POST   | `/v1/harvests`         | Record a harvest          |
| GET    | `/v1/harvests/summary` | Harvest summary analytics |

**Sensors** (`/v1/sensors`)

| Method | Path                        | Purpose                        |
| ------ | --------------------------- | ------------------------------ |
| GET    | `/v1/sensors`               | List sensors                   |
| POST   | `/v1/sensors`               | Register a sensor              |
| POST   | `/v1/sensors/:id/readings`  | Submit sensor readings         |
| GET    | `/v1/sensors/:id/readings`  | Get sensor readings            |
| POST   | `/v1/sensors/:id/alerts`    | Configure sensor alerts        |
| GET    | `/v1/sensors/:id/alerts`    | Get sensor alert configuration |
| GET    | `/v1/sensors/dashboard`     | Sensor dashboard               |
| POST   | `/v1/sensors/:id/calibrate` | Calibrate a sensor             |
| GET    | `/v1/sensors/summary`       | Sensor summary statistics      |

**Weather** (`/v1/weather`)

| Method | Path                        | Purpose                             |
| ------ | --------------------------- | ----------------------------------- |
| GET    | `/v1/weather/current`       | Current weather conditions (cached) |
| GET    | `/v1/weather/forecast`      | Weather forecast (cached)           |
| GET    | `/v1/weather/alerts`        | Active weather alerts               |
| GET    | `/v1/weather/history`       | Historical weather data             |
| GET    | `/v1/weather/garden-impact` | Weather impact on garden            |
| POST   | `/v1/weather/location`      | Set garden weather location         |

**Observations** (`/v1/observations`)

| Method | Path               | Purpose               |
| ------ | ------------------ | --------------------- |
| GET    | `/v1/observations` | List observations     |
| POST   | `/v1/observations` | Create an observation |

**AI** (`/v1/ai`)

| Method | Path                     | Purpose                  |
| ------ | ------------------------ | ------------------------ |
| POST   | `/v1/ai/identify-plant`  | Identify a plant         |
| POST   | `/v1/ai/diagnose`        | Diagnose plant problems  |
| GET    | `/v1/ai/planting-plan`   | Generate a planting plan |
| POST   | `/v1/ai/optimize-layout` | Optimize garden layout   |
| GET    | `/v1/ai/harvest-predict` | Predict harvest timing   |
| POST   | `/v1/ai/chat`            | Gardening assistant chat |

**Community** (`/v1/community`)

| Method | Path                               | Purpose                      |
| ------ | ---------------------------------- | ---------------------------- |
| GET    | `/v1/community/feed`               | Community feed               |
| POST   | `/v1/community/posts`              | Create a community post      |
| GET    | `/v1/community/posts/:id`          | Get a community post         |
| POST   | `/v1/community/posts/:id/comments` | Comment on a post            |
| POST   | `/v1/community/posts/:id/like`     | Like / unlike a post         |
| GET    | `/v1/community/gardens`            | Browse public gardens        |
| GET    | `/v1/community/leaderboard`        | Community leaderboard        |
| POST   | `/v1/community/challenges`         | Create a community challenge |
| GET    | `/v1/community/challenges`         | List community challenges    |

**Info** — `GET /v1/info` (authenticated) returns API metadata including the
domain list: `gardens`, `plants`, `plantings`, `tasks`, `harvests`, `sensors`,
`weather`, `observations`, `ai`, `community`.

### 6.6 API Configuration (`config.ts`)

All configuration is read from environment variables and validated by a Zod
schema in `config.ts` via `loadConfig()`. A `superRefine` check rejects the
default `JWT_SECRET` in production (it must be overridden) and enforces
`DB_POOL_MIN ≤ DB_POOL_MAX`. The table below lists every variable with its
default value and any notable constraints.

| Variable                      | Default                                                 | Notes                                 |
| ----------------------------- | ------------------------------------------------------- | ------------------------------------- | ---------- | ----- |
| `PORT`                        | `3030`                                                  | 1–65535                               |
| `HOST`                        | `127.0.0.1`                                             |                                       |
| `NODE_ENV`                    | `development`                                           | `development                          | production | test` |
| `JWT_SECRET`                  | `demeter-dev-secret-change-me`                          | must be overridden in production      |
| `JWT_ISSUER`                  | `demeter-api`                                           |                                       |
| `JWT_ACCESS_TOKEN_TTL`        | `900`                                                   | seconds                               |
| `JWT_REFRESH_TOKEN_TTL`       | `604800`                                                | seconds                               |
| `DATABASE_URL`                | `postgresql://oshun:oshun_dev@localhost:5432/oshun_dev` |                                       |
| `DB_POOL_MIN` / `DB_POOL_MAX` | `2` / `10`                                              |                                       |
| `REDIS_URL`                   | `redis://localhost:6379`                                |                                       |
| `RATE_LIMIT_MAX`              | `100`                                                   | requests per window                   |
| `RATE_LIMIT_WINDOW_MS`        | `60000`                                                 | ≥1000                                 |
| `LOG_LEVEL`                   | `info`                                                  | `trace`…`fatal`                       |
| `METRICS_PORT`                | `9091`                                                  |                                       |
| `CORS_ORIGIN`                 | `*`                                                     |                                       |
| `WEATHER_API_KEY`             | `''`                                                    | optional weather provider key         |
| `IOT_API_KEY`                 | `''`                                                    | optional IoT device key               |
| `OAUTH_GOOGLE_CLIENT_ID`      | `''`                                                    | for Google ID-token verification      |
| `OAUTH_APPLE_CLIENT_ID`       | `''`                                                    | for Apple identity-token verification |
| `MAGIC_LINK_SECRET`           | `demeter-magic-link-dev-secret`                         | signs magic-link tokens               |
| `SESSION_MAX_PER_USER`        | `10`                                                    | 1–100                                 |

### 6.7 Error Responses

Errors return a consistent body:
`{ error: { code, message, details?, requestId? } }`. RBAC failures use codes
`AUTH_REQUIRED` (401), `INSUFFICIENT_ROLE` (403), and `INSUFFICIENT_PERMISSION`
(403).

---

## 7. Application — `@demeter/web`

`apps/demeter/web` is a React single-page application built with Vite. Stack:
React, React Router v6, TanStack Query, Zustand, Tailwind CSS, Recharts, and
`date-fns`. It ships ~62 page components under `src/pages/` covering
authentication, dashboard, gardens (including a 2D and a 3D garden planner),
plants, plantings, tasks, harvests, sensors, weather, journal, analytics,
community, profile, and settings. API hooks live under `src/api/hooks/`; shared
UI is under `src/components/` (charts, forms, layout, tables, primitives).
End-to-end tests use Playwright (`e2e/`), and unit tests use Vitest.

---

## 8. Application — `@demeter/mobile`

`apps/demeter/mobile` is a React Native app on Expo SDK 52. Stack: React
Navigation (native-stack + bottom-tabs), TanStack Query, Zustand, and Expo
modules (`expo-notifications`, `expo-secure-store`,
`@react-native-async-storage/async-storage`). It ships ~50 screens under
`src/screens/` across auth, gardens, plants, journal, sensors, tasks, weather,
community, AI, and settings. The mobile app includes an offline subsystem
(`src/services/offline/` — action queue, conflict resolution, sync engine,
plant/weather caches, offline photo manager) and a widgets/integration layer
(`src/services/widgets/` — Siri Shortcuts, Google Assistant, Watch, quick
actions, notification actions, widget data provider). Tests use Jest with
`jest-expo`.

---

## 9. Persistence and Local Infrastructure

- **Database** — PostgreSQL. The Drizzle schema is in `@demeter/core`; the API
  wires Drizzle to a `pg.Pool` in the database plugin.
- **Cache / rate-limit / sessions** — Redis (ioredis).
- **Local dev** — `apps/demeter/docker-compose.yml` starts
  `pgvector/pgvector:pg16` PostgreSQL (host port `5433` to avoid colliding with
  the root compose), Redis 7, and optionally the API itself. The API container
  build is defined by `apps/demeter/api/Dockerfile`;
  `apps/demeter/api/scripts/init-db.sql` initializes the database.

---

## 10. Configuration and Project Tags

- **Library tags** — `["scope:demeter", "layer:domain", "type:lib"]`.
- **API tags** — `["scope:demeter", "type:app", "layer:service"]`.
- **Module format** — ESM (`"type": "module"`) across all packages.
- **Library build** — `@nx/js:tsc`; the API builds with a `nx:run-commands`
  executor invoking `tsc`.
- **Testing** — Vitest for libraries and the API/web; Jest (`jest-expo`) for
  mobile; Playwright for web e2e.

### Common Build Commands

```bash
# Test a specific library
pnpm nx test @demeter/core
pnpm nx test @demeter/plants

# Build / test all Demeter libraries
pnpm nx run-many --target=build --projects=tag:scope:demeter
pnpm nx run-many --target=test  --projects=tag:scope:demeter

# Run the API in development
pnpm nx dev @demeter/api
```

---

## 11. Cross-Domain Integration

Demeter's scope is home gardening and small-scale cultivation. The four related
domains in the table below each handle an adjacent concern. Understanding these
boundaries is important when deciding where to add a new feature.

| Domain | Relationship                                                                        |
| ------ | ----------------------------------------------------------------------------------- |
| Hestia | Seasonal ingredient sourcing; preservation/cooking of harvested produce overlaps    |
| Asase  | Commercial agricultural operations intelligence (Demeter scopes home/small-scale)   |
| Airmid | Botanical / phytotherapy intelligence (Demeter scopes growing, not therapeutic use) |
| Gaia   | Sovereign weather and climate ML source for advanced forecasts                      |

**Hestia** — The boundary runs through the post-harvest moment. Demeter records
yields, tracks preserved inventory, and generates harvest forecasts. Hestia
picks up from there for meal planning, recipe recommendations, and seasonal
ingredient sourcing. Yield records and preservation inventories cross this
boundary as data.

**Asase** — Demeter and Asase share the agricultural domain but differ in scale.
Demeter serves the individual home grower or community garden; Asase handles
commercial farm operations, commodity pricing, supply chain logistics, and farm
management at scale. Features that only make sense for a commercial operation
belong in Asase.

**Airmid** — Both domains understand plant biology. The dividing line is
purpose: Demeter knows how to cultivate a plant (spacing, soil, companions,
harvest timing); Airmid knows what it does in the body (active compounds,
therapeutic applications, drug interactions). A feature about growing herbs
belongs in Demeter; a feature about dosing or clinical evidence belongs in
Airmid.

**Gaia** — Demeter consumes Gaia's forecast products for localized garden
weather alerts, GDD enrichment, irrigation timing, and chill-hour analysis.
Demeter does not generate forecasts or own forecast skill verification — that is
Gaia's responsibility. The separation keeps meteorological modelling centralized
and avoids each domain implementing its own weather data pipeline.

---

## 12. Acceptance Criteria

A Demeter library or application is considered complete when:

1. **Schema fidelity** — types are inferred from Zod schemas in `@demeter/core`;
   the Drizzle schema's 17 tables and 20 enums match the entity model.
2. **Domain-specific algorithms** — calculations implement real horticultural
   science: GDD accumulation, chill hours, DLI/PPFD/VPD, EC↔TDS conversion,
   altitude-adjusted canning times, botulism risk, biological efficiency,
   isolation distances, haversine distance.
3. **Food safety** — preservation logic enforces USDA/NCHFP rules: water-bath
   canning is rejected for low-acid foods, altitude adjustments are mandatory,
   and fermentation/canning carry pH-based safety assessment.
4. **API contract** — every endpoint validates input with Zod, enforces JWT auth
   where required, applies RBAC/ownership checks, and returns the standard error
   envelope.
5. **Tests** — Vitest/Jest suites verify domain correctness against
   known-correct values, not just data flow. Each library source module has a
   co-located `.spec.ts`; the API has route, auth, RBAC, rate-limit, and
   security tests; the web app has unit and Playwright e2e tests.
