# Gaia Domain — Technical Specifications

## 1. Status and Scope

Gaia is a **planned domain**. No code exists yet: there are no `apps/gaia/*`,
`libs/gaia/*`, or `services/gaia/*` packages in the monorepo, and no `@gaia/*`
path mappings in `tsconfig.base.json`. This document is therefore a **planned /
design-level specification** — it describes the domain as designed in
`TODOS/phase-175.md`, `DOMAINS/gaia/features.md`, and
`DOMAINS/gaia/architecture.md`, not implemented code. Every schema, enum,
endpoint, event, and state below is grounded in those three documents. Items not
yet defined in any of them are not invented here.

Gaia is the sovereign weather and climate ML platform: an end-to-end data,
training, and serving pipeline that turns global weather and climate
observations into operational forecast products and weather-impact APIs for the
rest of Oshun. It replicates and extends the frontier AI-for-Earth-sciences
stack — GraphCast, GenCast, FGN, and WeatherNext (Google DeepMind),
Pangu-Weather (Huawei), FourCastNet and StormCast (NVIDIA), and Aurora
(Microsoft) — across medium-range deterministic forecasting, probabilistic
ensemble forecasting, direct tropical-cyclone prediction, minute-scale
precipitation nowcasting, decadal climate emulation, and statistical
downscaling.

Gaia in `DOMAINS/` means Earth-system forecasting and climate ML. It is distinct
from Nyx's `gaia` star-catalog references (the ESA Gaia astrometry mission);
those live under `libs/nyx/catalogs/gaia` and are unrelated.

## 2. Package Prefix and Library Inventory

All Gaia libraries share the `@gaia/*` package prefix and will live under
`libs/gaia/*` in the monorepo. Phase 175 is the canonical task-level checklist.

- Library package prefix: `@gaia/*`
- Planned library paths: `libs/gaia/*`
- Canonical task-level checklist: `TODOS/phase-175.md` (Phase 175)

Phase 175 specifies eight planned libraries, covering the complete pipeline from
raw data ingestion through forecast serving.

| Package                  | Planned path                 | Specification scope                                                                                                                                                                                                                          |
| ------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@gaia/weather-data`     | `libs/gaia/weather-data`     | Reanalysis, operational analysis, observation, radar, satellite, station, lightning, and cyclone best-track ingestion; GRIB2/Zarr conversion; the `WeatherSample` schema; temporal splits; normalization statistics; sampling and cataloging |
| `@gaia/graphcast`        | `libs/gaia/graphcast`        | Icosahedral multi-mesh generation; grid-to-mesh encoder, interaction-network processor, mesh-to-grid decoder; autoregressive deterministic rollouts; GraphCast-Sovereign training plans; rollout-stability diagnostics                       |
| `@gaia/gencast`          | `libs/gaia/gencast`          | State-diffusion residual model over the spherical mesh; DPM-Solver++ and stochastic Heun ensemble samplers; CRPS, reliability, rank-histogram, spread-skill, and tail calibration diagnostics                                                |
| `@gaia/cyclone-forecast` | `libs/gaia/cyclone-forecast` | Differentiable cyclone heads (formation, track, intensity, pressure, wind-radius quadrants); trajectory decoder; `CycloneForecastProduct` distributions; ATCF deck products                                                                  |
| `@gaia/nowcast`          | `libs/gaia/nowcast`          | Radar/satellite/lightning precipitation nowcasting; MetNet-3-class deterministic model; DGMR-class ensemble GAN; convective-mode classifier; nowcast verification                                                                            |
| `@gaia/climate-emulator` | `libs/gaia/climate-emulator` | Spherical-FNO / ACE-class climate emulation; long (decadal, up to 100-year) rollouts; tunable forcing; coupled feedbacks; decadal hindcasts                                                                                                  |
| `@gaia/downscale`        | `libs/gaia/downscale`        | Fine-scale residual-diffusion downscaling; station bias correction; multivariate quantile mapping; urban heat-island downscaling                                                                                                             |
| `@gaia/forecast-serving` | `libs/gaia/forecast-serving` | Forecast REST/OpenAPI BFF; operational cycle runner; forecast-product warehouse; STAC/OGC catalogs; CAP severe-weather alerts; downstream adapters; SLO dashboards                                                                           |

## 3. Technology Stack

Gaia is a polyglot domain. The Phase 175 header fixes the language split based
on each sub-domain's performance and ecosystem requirements: Python for the
model-training workloads where JAX and PyTorch tooling is richest, Rust for the
high-throughput data conversion workers, and TypeScript for orchestration and
API services that integrate with the rest of the Oshun monorepo.

| Language   | Approximate share | Responsibility                                                          |
| ---------- | ----------------- | ----------------------------------------------------------------------- |
| Python     | ~60%              | Model training (PyTorch with JAX/Haiku parity) and reanalysis ingestion |
| Rust       | ~20%              | Zarr/GRIB conversion and operational ensemble workers                   |
| TypeScript | ~20%              | Orchestration, forecast APIs, and weather-impact integration            |

Distributed training targets named in Phase 175: TPU-v5e and H100-class pods,
with JAX `pjit` or PyTorch FSDP2, BF16 mixed precision, and activation
checkpointing. Operational ensemble generation targets a single H100 or B200
accelerator.

## 4. Core Data Contracts

The TypeScript contracts below are the canonical domain objects used by the
TypeScript orchestration and serving layers. `@gaia/weather-data` owns
`WeatherGrid` and `WeatherSample`; `@gaia/forecast-serving` owns
`ForecastRequest` and `ForecastProduct`. These interfaces are the primary
cross-package API surface — every other `@gaia/*` library either produces or
consumes values conforming to these types.

```typescript
type GaiaGridType = 'lat_lon' | 'icosahedral_mesh' | 'regional_projected';

interface WeatherGrid {
  id: string;
  type: GaiaGridType;
  resolution: string;
  bounds?: {
    north: number;
    south: number;
    east: number;
    west: number;
  };
  meshLevel?: number;
}

interface WeatherSample {
  id: string;
  validTime: string;
  leadTimeHours: number;
  grid: WeatherGrid;
  pressureLevels: number[];
  surfaceVariables: string[];
  upperAirVariables: string[];
  source:
    | 'era5'
    | 'era5_land'
    | 'hres'
    | 'gfs'
    | 'merra2'
    | 'radar'
    | 'satellite'
    | 'station'
    | 'lightning'
    | 'ibtracs';
  zarrPath: string;
  provenanceHash: string;
  normalizationVersion?: string;
}

interface ForecastRequest {
  cycleTime: string;
  horizonHours: number;
  region?: WeatherGrid['bounds'];
  product:
    | 'deterministic'
    | 'ensemble'
    | 'cyclone'
    | 'nowcast'
    | 'climate_scenario'
    | 'downscaled';
  variables: string[];
  outputFormat: 'json' | 'zarr' | 'grib2' | 'stac' | 'ogc_edr';
}

interface ForecastProduct {
  id: string;
  modelId: string;
  modelVersion: string;
  cycleTime: string;
  horizonHours: number;
  grid: WeatherGrid;
  variables: string[];
  productUris: string[];
  skillSummary?: SkillSummary;
  publicationStatus: 'draft' | 'published' | 'rolled_back';
}
```

### 4.1 `GaiaGridType` (literal union)

Three grid representations are used throughout the domain. The type controls how
coordinates and chunk layouts are interpreted at every layer from storage to
serving.

| Value                | Meaning                                                                                                          |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `lat_lon`            | A regular latitude/longitude grid (the native form of ERA5, GFS, and similar global products).                   |
| `icosahedral_mesh`   | A hierarchical icosahedral triangular mesh (the mesh the deterministic GNN and diffusion model operate on).      |
| `regional_projected` | A regional grid in a projected coordinate system (the form used by regional reanalyses and downscaled products). |

### 4.2 `WeatherGrid` interface

`WeatherGrid` describes the spatial grid a `WeatherSample` or `ForecastProduct`
is defined on. Every sample and every product carries a `WeatherGrid` so
consumers always know the coordinate system and resolution of the data they are
reading.

| Field        | Type                                       | Required | Meaning                                                                                                  |
| ------------ | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `id`         | `string`                                   | Yes      | Stable identifier for the grid definition.                                                               |
| `type`       | `GaiaGridType`                             | Yes      | Which of the three grid kinds this grid is.                                                              |
| `resolution` | `string`                                   | Yes      | The grid resolution as a string (e.g. a degree spacing such as `0.25`, or a length such as `2km`).       |
| `bounds`     | `{ north; south; east; west }` of `number` | No       | The geographic bounding box (degrees). Absent for global grids; present for regional grids and requests. |
| `meshLevel`  | `number`                                   | No       | For `icosahedral_mesh` grids, the mesh refinement level (M0 through M6).                                 |

### 4.3 `WeatherSample` interface

`WeatherSample` is the canonical sample unit of the domain. `@gaia/weather-data`
produces `WeatherSample` records from every ingested source, and training and
serving jobs read them. The schema is designed around full traceability: every
field exists so a sample can be traced back to its exact source data and the
normalization applied, which is required for auditable model checkpoints.

| Field                  | Type                                | Required | Meaning                                                                                     |
| ---------------------- | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `id`                   | `string`                            | Yes      | Stable identifier for the sample.                                                           |
| `validTime`            | `string`                            | Yes      | The timestamp the sample's fields are valid for.                                            |
| `leadTimeHours`        | `number`                            | Yes      | Lead time in hours from the analysis/initialization time to `validTime`.                    |
| `grid`                 | `WeatherGrid`                       | Yes      | The grid the sample's fields are defined on.                                                |
| `pressureLevels`       | `number[]`                          | Yes      | The pressure levels (hPa) carried by the sample. ERA5 ingestion targets 37 pressure levels. |
| `surfaceVariables`     | `string[]`                          | Yes      | The surface (single-level) variable names present in the sample.                            |
| `upperAirVariables`    | `string[]`                          | Yes      | The upper-air (pressure-level) variable names present in the sample.                        |
| `source`               | `WeatherSampleSource` (union, §4.4) | Yes      | Which data source the sample was ingested from.                                             |
| `zarrPath`             | `string`                            | Yes      | The object-storage path to the sample's Zarr store.                                         |
| `provenanceHash`       | `string`                            | Yes      | A content hash binding the sample to its exact source data.                                 |
| `normalizationVersion` | `string`                            | No       | The version of the normalization statistics applied to (or associated with) the sample.     |

The reanalysis-ingestion design (Phase 175.1.1.1) targets ERA5 surface fields
including 2m temperature, 10m u/v winds, mean sea-level pressure, total
precipitation, and 2m dewpoint, plus 37 pressure levels carrying geopotential,
u/v wind, vertical velocity (w), temperature, and specific humidity. These are
the variables `surfaceVariables`, `upperAirVariables`, and `pressureLevels`
enumerate; the schema itself stores them as open string lists rather than a
fixed enum.

### 4.4 `WeatherSample.source` (literal union)

The `source` field records which ingestion client and data provider produced the
sample. Each value maps directly to a distinct ingestion client specified in
Phase 175.1, and the mapping is intentionally one-to-one so provenance is
unambiguous.

| Value       | Source                                                                                                                      |
| ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| `era5`      | ECMWF ERA5 reanalysis (Copernicus CDS), 1940–present, hourly 0.25°, surface and 37 pressure levels.                         |
| `era5_land` | ERA5-Land high-resolution surface reanalysis, 0.1° hourly, 1950–present; downscaling priors and hydrology.                  |
| `hres`      | ECMWF IFS HRES operational analysis (MARS), 9 km deterministic, 00/06/12/18 UTC; live initial conditions.                   |
| `gfs`       | NOAA GFS operational analysis (NOMADS), 13 km, four cycles; fallback initial conditions and cross-validation.               |
| `merra2`    | NASA MERRA-2 reanalysis (GES DISC), 1980–present, 0.5° × 0.625°; multi-reanalysis agreement and aerosol inputs.             |
| `radar`     | Weather radar — NEXRAD Level-II (US, 250 m, 2-min cadence) and OPERA composites (Europe, 2 km, 5-min).                      |
| `satellite` | Geostationary satellite — GOES-R, MTG, Himawari (16 bands, 10-min full disk) and GPM IMERG precipitation.                   |
| `station`   | Surface station networks — METAR, SYNOP, BUFR, and ASOS observations.                                                       |
| `lightning` | Lightning detection — GLM (from GOES) and EUCLID (Europe).                                                                  |
| `ibtracs`   | IBTrACS tropical-cyclone best-track archive, 1842–present, all basins; cyclone training labels and evaluation ground truth. |

### 4.5 `ForecastRequest` interface

`ForecastRequest` describes a forecast job submitted to
`@gaia/forecast-serving`. The `product` field is the primary routing key: it
tells the serving layer which `@gaia/*` model library to invoke (see §4.6). The
`outputFormat` field controls serialization without changing what is computed.

| Field          | Type                                 | Required | Meaning                                                              |
| -------------- | ------------------------------------ | -------- | -------------------------------------------------------------------- |
| `cycleTime`    | `string`                             | Yes      | The analysis cycle time the forecast is initialized from.            |
| `horizonHours` | `number`                             | Yes      | The forecast horizon in hours.                                       |
| `region`       | `WeatherGrid['bounds']`              | No       | An optional bounding box restricting the forecast to a region.       |
| `product`      | `ForecastProductType` (union, §4.6)  | Yes      | A discriminator selecting which forecast product class is requested. |
| `variables`    | `string[]`                           | Yes      | The variable names the forecast should produce.                      |
| `outputFormat` | `ForecastOutputFormat` (union, §4.7) | Yes      | The serialization format the product should be delivered in.         |

### 4.6 `ForecastRequest.product` (literal union)

The `product` field routes a `ForecastRequest` to the appropriate model library.
Each value maps to exactly one `@gaia/*` package, keeping routing logic
centralized and the model libraries independently deployable.

| Value              | Routed to                | Meaning                                                            |
| ------------------ | ------------------------ | ------------------------------------------------------------------ |
| `deterministic`    | `@gaia/graphcast`        | A single deterministic spherical-GNN forecast.                     |
| `ensemble`         | `@gaia/gencast`          | A probabilistic diffusion ensemble forecast.                       |
| `cyclone`          | `@gaia/cyclone-forecast` | A direct tropical-cyclone forecast.                                |
| `nowcast`          | `@gaia/nowcast`          | A minute-scale precipitation nowcast.                              |
| `climate_scenario` | `@gaia/climate-emulator` | A climate-emulation scenario rollout.                              |
| `downscaled`       | `@gaia/downscale`        | A fine-scale downscaled product derived from an existing forecast. |

### 4.7 `ForecastRequest.outputFormat` (literal union)

The `outputFormat` field lets different consumer types pull data in the format
their tooling expects. A shell surface (e.g. Uzume's event dashboard) pulls
JSON; a scientific user or retraining job pulls Zarr or GRIB2 directly from
object storage using signed URLs.

| Value     | Meaning                                                           |
| --------- | ----------------------------------------------------------------- |
| `json`    | A JSON summary suitable for shell surfaces and APIs.              |
| `zarr`    | A Zarr store — the native form of the forecast-product warehouse. |
| `grib2`   | A GRIB2-compatible export bundle for interoperability.            |
| `stac`    | A STAC 1.0 item describing the product.                           |
| `ogc_edr` | An OGC Environmental Data Retrieval (EDR) product.                |

### 4.8 `ForecastProduct` interface

`ForecastProduct` is the resolved output of a `ForecastRequest`.
`@gaia/forecast-serving` persists `ForecastProduct` records to the warehouse and
registers them in the catalogs. Every field is designed for auditability:
consumers must always be able to trace a product back to the model version and
source data that produced it.

| Field               | Type                                      | Required | Meaning                                                                        |
| ------------------- | ----------------------------------------- | -------- | ------------------------------------------------------------------------------ |
| `id`                | `string`                                  | Yes      | Stable product identifier.                                                     |
| `modelId`           | `string`                                  | Yes      | Identifier of the model that produced the product.                             |
| `modelVersion`      | `string`                                  | Yes      | The version of that model.                                                     |
| `cycleTime`         | `string`                                  | Yes      | The analysis cycle time the forecast was initialized from.                     |
| `horizonHours`      | `number`                                  | Yes      | The forecast horizon in hours.                                                 |
| `grid`              | `WeatherGrid`                             | Yes      | The grid the product's fields are defined on.                                  |
| `variables`         | `string[]`                                | Yes      | The variable names the product carries.                                        |
| `productUris`       | `string[]`                                | Yes      | Signed URIs to the product files.                                              |
| `skillSummary`      | `SkillSummary` (§4.9)                     | No       | A summary of the product's evaluation skill, present where evaluation has run. |
| `publicationStatus` | `ProductPublicationStatus` (union, §4.10) | Yes      | The product's lifecycle state.                                                 |

### 4.9 `SkillSummary` interface

`SkillSummary` is referenced as the optional `skillSummary` field of
`ForecastProduct`. The features and architecture documents describe it
qualitatively — it is "a summary of the product's evaluation skill" reported
"where available" — but they do not enumerate its fields. The concrete field
list is **not specified** in `features.md`, `architecture.md`, or
`TODOS/phase-175.md`, and is therefore left to implementation. The metrics that
feed product skill are catalogued in §14 (Evaluation Requirements).

### 4.10 `ForecastProduct.publicationStatus` (product lifecycle state machine)

`publicationStatus` tracks a product through its operational lifecycle. The
three states encode whether a product is safe to surface to downstream
consumers. The `rolled_back` state is important for auditability: it ensures
that a bad cycle's products are retained and inspectable rather than silently
deleted.

| State         | Meaning                                                                                                                                                                       |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `draft`       | Initial state. The product exists but has not been published. A cycle that fails any publication gate leaves its products in `draft`.                                         |
| `published`   | The product has passed all publication gates and is visible to downstream consumers. A `gaia.forecast.product.published` event fires on entry.                                |
| `rolled_back` | A previously published product found to be bad. The product is retained (not deleted) so the failure is auditable; downstream consumers fall back to the previous good cycle. |

The allowed transitions are:

| From        | To            | Trigger                                                                                                    |
| ----------- | ------------- | ---------------------------------------------------------------------------------------------------------- |
| `draft`     | `published`   | All three publication gates pass (stability diagnostics, skill-regression check, data-completeness check). |
| `draft`     | `draft`       | A publication gate fails. The product stays in `draft` and is not published.                               |
| `published` | `rolled_back` | A published cycle is later found bad and is rolled back.                                                   |

`rolled_back` is the terminal state. `draft` is terminal for products of a cycle
that never passes its gates — they remain `draft` rather than being deleted.

## 5. Cyclone Contracts (`@gaia/cyclone-forecast`)

Direct tropical-cyclone prediction produces a `CycloneForecastProduct` keyed by
`stormId` and `basin`, holding an ordered list of `CycloneForecastPoint` records
— one per forecast lead time. The two interfaces below define the complete wire
format for cyclone products.

```typescript
interface CycloneForecastPoint {
  leadTimeHours: number;
  probabilityOfCyclone: number;
  centerLat: number;
  centerLon: number;
  maxSustainedWindKt?: number;
  centralPressureHpa?: number;
  radiusOfMaximumWindNm?: number;
  r34QuadrantsNm?: [number, number, number, number];
  r50QuadrantsNm?: [number, number, number, number];
  r64QuadrantsNm?: [number, number, number, number];
  saffirSimpsonCategory?: 0 | 1 | 2 | 3 | 4 | 5;
  eyeFormationProbability?: number;
}

interface CycloneForecastProduct {
  stormId: string;
  basin: string;
  cycleTime: string;
  points: CycloneForecastPoint[];
  atcfUri?: string;
  uncertaintyConeUri?: string;
}
```

### 5.1 `CycloneForecastPoint` interface

One forecast point represents a single lead time for one storm. Points are
emitted along the standard forecast horizon — Phase 175.4 targets a 0–120h
trajectory distribution — so reading `points` in order gives the full predicted
track.

| Field                     | Type                               | Required | Meaning                                                                  |
| ------------------------- | ---------------------------------- | -------- | ------------------------------------------------------------------------ |
| `leadTimeHours`           | `number`                           | Yes      | Lead time in hours from the cycle time to this point.                    |
| `probabilityOfCyclone`    | `number`                           | Yes      | The model's confidence that a coherent cyclone exists at this lead time. |
| `centerLat`               | `number`                           | Yes      | Forecast cyclone-center latitude.                                        |
| `centerLon`               | `number`                           | Yes      | Forecast cyclone-center longitude.                                       |
| `maxSustainedWindKt`      | `number`                           | No       | Maximum sustained wind in knots.                                         |
| `centralPressureHpa`      | `number`                           | No       | Central minimum sea-level pressure in hectopascals.                      |
| `radiusOfMaximumWindNm`   | `number`                           | No       | Radius of maximum wind in nautical miles.                                |
| `r34QuadrantsNm`          | `[number, number, number, number]` | No       | NE, SE, SW, NW radii (nautical miles) of 34-knot wind.                   |
| `r50QuadrantsNm`          | `[number, number, number, number]` | No       | NE, SE, SW, NW radii (nautical miles) of 50-knot wind.                   |
| `r64QuadrantsNm`          | `[number, number, number, number]` | No       | NE, SE, SW, NW radii (nautical miles) of 64-knot wind.                   |
| `saffirSimpsonCategory`   | `0 \| 1 \| 2 \| 3 \| 4 \| 5`       | No       | Saffir-Simpson category, where `0` denotes tropical storm or weaker.     |
| `eyeFormationProbability` | `number`                           | No       | The probability that a closed eye has formed.                            |

Intensity and structure fields are optional per point because a low-probability
or pre-formation point may carry only position. `leadTimeHours`,
`probabilityOfCyclone`, `centerLat`, and `centerLon` are always present.

### 5.2 `saffirSimpsonCategory` (numeric literal union)

The category is encoded as a numeric literal union rather than a string to make
threshold comparisons cheap and unambiguous. Category 0 covers tropical storms
and sub-tropical systems that do not yet meet hurricane criteria.

| Value | Meaning                    |
| ----- | -------------------------- |
| `0`   | Tropical storm or weaker.  |
| `1`   | Saffir-Simpson Category 1. |
| `2`   | Saffir-Simpson Category 2. |
| `3`   | Saffir-Simpson Category 3. |
| `4`   | Saffir-Simpson Category 4. |
| `5`   | Saffir-Simpson Category 5. |

### 5.3 `CycloneForecastProduct` interface

`CycloneForecastProduct` is the top-level object returned by the cyclone
forecast endpoint. The `atcfUri` and `uncertaintyConeUri` fields are signed
object-storage URIs — they are optional because not every cycle will produce
both assets, but they must be present for any product that is consumed by
NHC-compatible emergency-response tooling.

| Field                | Type                     | Required | Meaning                                                                                         |
| -------------------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------- |
| `stormId`            | `string`                 | Yes      | Identifier of the storm the product forecasts.                                                  |
| `basin`              | `string`                 | Yes      | The cyclone basin the storm is in.                                                              |
| `cycleTime`          | `string`                 | Yes      | The analysis cycle time the forecast was initialized from.                                      |
| `points`             | `CycloneForecastPoint[]` | Yes      | The ordered per-lead-time forecast points; together they are the full track.                    |
| `atcfUri`            | `string`                 | No       | A signed URI to an ATCF-compatible deck for emergency-response systems that consume the format. |
| `uncertaintyConeUri` | `string`                 | No       | A signed URI to the track uncertainty cone.                                                     |

### 5.4 Model Design

The cyclone model is built on four numbered Phase 175 design points. Each point
must be implemented for the model to produce ATCF-compliant output.

- **Functional cyclone output (Phase 175.4.1.1).** At each 6-hour lead time the
  model jointly predicts probability-of-cyclone, center lat/lon, max sustained
  wind, central MSLP, radius of maximum wind, R34/R50/R64 quadrants, and an eye
  formation flag, as a differentiable head conditioned on the diffusion-backbone
  latent.
- **Trajectory decoder (Phase 175.4.1.2).** A Transformer decoder over the
  predicted cyclone centers, conditioned on the past 24h IBTrACS track plus the
  model latent, outputs a 0–120h trajectory distribution with stochastic
  sampling. Track and intensity are decoded jointly so they stay physically
  consistent.
- **Loss formulation (Phase 175.4.1.3).** Training uses a combined loss:
  position (great-circle Haversine), intensity (Huber on Vmax and MSLP),
  categorical class (Saffir-Simpson), and Brier score on formation. The model is
  penalized for a good track with a wrong intensity as well as the reverse.
- **ATCF output (Phase 175.4.1.4).** An automated NHC-compatible ATCF generator
  emits the technical-format deck (BASIN, CY, YYYYMMDDHH, TAU, Lat N/S, Lon E/W,
  Vmax, MSLP, category, R34/R50/R64) for ingestion by downstream
  emergency-response systems.

## 6. Nowcast Contracts (`@gaia/nowcast`)

Minute-scale precipitation nowcasting answers a `PrecipitationNowcastRequest`
and returns a `PrecipitationNowcastProduct`. The contracts below are the wire
format for the nowcast endpoint.

```typescript
interface PrecipitationNowcastRequest {
  issuedAt: string;
  horizonMinutes: number;
  cadenceMinutes: number;
  region: WeatherGrid['bounds'];
  thresholdsMmPerHour: number[];
  ensembleMembers?: number;
}

interface PrecipitationNowcastProduct {
  id: string;
  issuedAt: string;
  horizonMinutes: number;
  cadenceMinutes: number;
  productUris: string[];
  convectiveMode:
    | 'stratiform'
    | 'convective'
    | 'tropical'
    | 'frontal'
    | 'mixed';
  verification?: {
    csi: number;
    pod: number;
    far: number;
    fss: number;
  };
}
```

### 6.1 `PrecipitationNowcastRequest` interface

`region` is required (not optional) because nowcasting is inherently regional —
there is no meaningful global nowcast given the fine spatial resolution and the
radar/satellite inputs that anchor the model. `thresholdsMmPerHour` lets the
consumer specify which rain-rate thresholds they want probability fields for,
rather than receiving a fixed set.

| Field                 | Type                    | Required | Meaning                                                                                              |
| --------------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `issuedAt`            | `string`                | Yes      | The nowcast base time.                                                                               |
| `horizonMinutes`      | `number`                | Yes      | How far ahead the nowcast extends, within the 0–12 hour skill window.                                |
| `cadenceMinutes`      | `number`                | Yes      | The output time step.                                                                                |
| `region`              | `WeatherGrid['bounds']` | Yes      | The bounding box the nowcast covers. Required — nowcasting is always regional.                       |
| `thresholdsMmPerHour` | `number[]`              | Yes      | The rain-rate thresholds (mm/h) the user wants probability fields for (e.g. light, moderate, heavy). |
| `ensembleMembers`     | `number`                | No       | An ensemble-member count. When set, selects ensemble GAN nowcasting over a single deterministic run. |

### 6.2 `PrecipitationNowcastProduct` interface

The `verification` block is absent from freshly issued nowcasts — it cannot be
computed until the valid period has elapsed and stage observations are
available. Consumers should not treat a missing `verification` block as a
failure; it will be filled in after the fact.

| Field            | Type                                 | Required | Meaning                                                                                               |
| ---------------- | ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `id`             | `string`                             | Yes      | Stable product identifier.                                                                            |
| `issuedAt`       | `string`                             | Yes      | The nowcast base time (echoes the request).                                                           |
| `horizonMinutes` | `number`                             | Yes      | The nowcast horizon in minutes (echoes the request).                                                  |
| `cadenceMinutes` | `number`                             | Yes      | The output time step (echoes the request).                                                            |
| `productUris`    | `string[]`                           | Yes      | Signed URIs to the nowcast product files.                                                             |
| `convectiveMode` | `ConvectiveMode` (union, §6.3)       | Yes      | The classified convective regime of the precipitation.                                                |
| `verification`   | `{ csi; pod; far; fss }` of `number` | No       | Verification metrics, present once the valid period has elapsed. Absent for a freshly issued nowcast. |

### 6.3 `PrecipitationNowcastProduct.convectiveMode` (literal union)

`convectiveMode` encodes the precipitation regime classified by the model. The
mode matters to downstream consumers because the skill and predictability of a
nowcast differs significantly by regime: a convective cell evolves over minutes
and is hard to track beyond 30–60 minutes, while a stratiform shield is
predictable over several hours. A convective-mode classifier (Phase 175.5.1.3)
derives this value from radar, satellite, and lightning inputs.

| Value        | Meaning                                               |
| ------------ | ----------------------------------------------------- |
| `stratiform` | Widespread, slowly-evolving stratiform precipitation. |
| `convective` | Fast-evolving, short-lived convective precipitation.  |
| `tropical`   | Tropical-regime precipitation.                        |
| `frontal`    | Frontal-system precipitation.                         |
| `mixed`      | A mix of regimes.                                     |

### 6.4 `PrecipitationNowcastProduct.verification` block

The four verification metrics in the `verification` block are standard
deterministic precipitation-verification scores widely used in operational NWP.
They are computed against Stage IV and radar-gauge blends (Phase 175.5.1.4
specifies verification at 0.1/1/5/10 mm h⁻¹ thresholds).

| Field | Type     | Meaning                   |
| ----- | -------- | ------------------------- |
| `csi` | `number` | Critical success index.   |
| `pod` | `number` | Probability of detection. |
| `far` | `number` | False-alarm ratio.        |
| `fss` | `number` | Fractional skill score.   |

### 6.5 Model Design

Two model variants share the same output contract, selected by the presence of
`ensembleMembers` in the request. A consumer that needs only a single
best-estimate track can use the lighter deterministic model; a consumer that
needs uncertainty bounds (e.g. for event-cancellation risk thresholds) sets
`ensembleMembers` to receive a spread.

- **Deterministic model (Phase 175.5.1.1).** A MetNet-3-class axial transformer
  over stacked radar composites (last 90 minutes, 2-minute cadence),
  geostationary satellite, topography, and NWP boundary conditions, producing
  0–12 hour precipitation at 1 km, 5-minute resolution. Radar and satellite
  supply the recent motion and convection signal; NWP boundary conditions keep
  the longer lead times physically anchored.
- **Ensemble model (Phase 175.5.1.2).** A DGMR-class conditional GAN generates
  an ensemble of precipitation fields (a 4-member radar ensemble is the
  baseline), so downstream consumers see spread rather than a single track.

## 7. Climate and Downscaling Contracts

`ClimateScenarioRequest` and `DownscalingRequest` serve two related but distinct
needs: the climate emulator produces long-horizon scenario projections under
tunable greenhouse-gas forcing, while the downscaler refines an existing coarse
forecast to finer spatial scale. Their contracts are defined below.

```typescript
interface ClimateScenarioRequest {
  baselinePeriod: string;
  rolloutYears: number;
  forcing: {
    co2Ppm?: number;
    ch4Ppb?: number;
    aerosolScenario?: string;
    ssp?: 'ssp2_45' | 'ssp5_85' | 'custom';
  };
  variables: string[];
}

interface DownscalingRequest {
  sourceForecastId: string;
  targetResolution: '2km' | '1km' | '50m_urban' | 'station';
  method:
    | 'residual_diffusion'
    | 'station_bias'
    | 'quantile_mapping'
    | 'urban_uhi';
  region?: WeatherGrid['bounds'];
  stationIds?: string[];
}
```

### 7.1 `ClimateScenarioRequest` interface (`@gaia/climate-emulator`)

`ClimateScenarioRequest` describes a multi-decade climate-emulation rollout. The
`forcing` block is the mechanism that distinguishes scenarios: consumers can
select a standard IPCC pathway (SSP2-4.5 or SSP5-8.5) or specify explicit
greenhouse-gas concentrations for a custom scenario.

| Field            | Type                    | Required | Meaning                                                          |
| ---------------- | ----------------------- | -------- | ---------------------------------------------------------------- |
| `baselinePeriod` | `string`                | Yes      | The reference climate period the rollout departs from.           |
| `rolloutYears`   | `number`                | Yes      | The rollout length in years, up to the validated 100-year range. |
| `forcing`        | `ClimateForcing` (§7.2) | Yes      | The forcing block driving the rollout.                           |
| `variables`      | `string[]`              | Yes      | The variable names the rollout should produce.                   |

### 7.2 `ClimateScenarioRequest.forcing` block

All four fields in the `forcing` block are optional because a standard SSP
pathway is self-contained (no explicit gas concentrations needed), while the
`custom` path requires explicit `co2Ppm` and `ch4Ppb`. An implementation must
validate that a `custom` `ssp` is accompanied by at least one explicit
concentration.

| Field             | Type                                 | Required | Meaning                                                         |
| ----------------- | ------------------------------------ | -------- | --------------------------------------------------------------- |
| `co2Ppm`          | `number`                             | No       | An explicit atmospheric CO₂ concentration in parts per million. |
| `ch4Ppb`          | `number`                             | No       | An explicit atmospheric CH₄ concentration in parts per billion. |
| `aerosolScenario` | `string`                             | No       | An aerosol scenario identifier.                                 |
| `ssp`             | `'ssp2_45' \| 'ssp5_85' \| 'custom'` | No       | A Shared Socioeconomic Pathway selector.                        |

### 7.3 `ClimateScenarioRequest.forcing.ssp` (literal union)

The two standard SSP pathways map to IPCC scenario definitions. The `custom`
value unlocks the explicit gas-concentration path, useful for sensitivity
analyses that do not correspond to any published SSP.

| Value     | Meaning                                                                                                                                |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `ssp2_45` | The SSP2-4.5 standard pathway.                                                                                                         |
| `ssp5_85` | The SSP5-8.5 standard pathway.                                                                                                         |
| `custom`  | A custom pathway. This is the path that uses the explicit `co2Ppm` and `ch4Ppb` gas concentrations rather than a standard SSP pathway. |

### 7.4 `DownscalingRequest` interface (`@gaia/downscale`)

`DownscalingRequest` takes a coarse `ForecastProduct` identified by
`sourceForecastId` and produces a finer-scale output. The `method` and
`targetResolution` fields are related — certain methods are designed for
specific resolution targets (see §7.6), and the implementation should validate
that the chosen combination is supported.

| Field              | Type                                         | Required | Meaning                                                       |
| ------------------ | -------------------------------------------- | -------- | ------------------------------------------------------------- |
| `sourceForecastId` | `string`                                     | Yes      | The id of the coarse `ForecastProduct` to refine.             |
| `targetResolution` | `'2km' \| '1km' \| '50m_urban' \| 'station'` | Yes      | The target resolution of the downscaled product.              |
| `method`           | `DownscalingMethod` (union, §7.6)            | Yes      | The downscaling method to apply.                              |
| `region`           | `WeatherGrid['bounds']`                      | No       | An optional bounding box restricting the downscaled region.   |
| `stationIds`       | `string[]`                                   | No       | A station-id list, used when `targetResolution` is `station`. |

### 7.5 `DownscalingRequest.targetResolution` (literal union)

The four resolution targets span the range from gridded regional products to
point-site station corrections.

| Value       | Meaning                                                                     |
| ----------- | --------------------------------------------------------------------------- |
| `2km`       | A 2 km grid (the target of CorrDiff-class 25 km → 2 km residual diffusion). |
| `1km`       | A 1 km grid.                                                                |
| `50m_urban` | A 50 m urban grid for urban heat-island-aware downscaling.                  |
| `station`   | Specific station locations (uses the `stationIds` list).                    |

### 7.6 `DownscalingRequest.method` (literal union)

Each method is designed for a specific class of downscaling problem. The
associations between method and target are: `residual_diffusion` → `2km` or
`1km` grid refinement; `station_bias` → `station` site correction;
`quantile_mapping` → derived-variable distribution correction; `urban_uhi` →
`50m_urban` city-scale heat.

| Value                | Meaning                                                                                                                                                                                                                                                        |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `residual_diffusion` | CorrDiff-class residual diffusion (used for 25 km → 2 km grid refinement), conditioned on the deterministic/diffusion forecasts plus high-resolution topography and land-use.                                                                                  |
| `station_bias`       | Station-level bias correction at METAR/SYNOP locations, per-station MLP/XGBoost heads consuming the 25 km ensemble for 1–10 day site calibration.                                                                                                              |
| `quantile_mapping`   | Multivariate quantile-mapping bias correction. Covers derived variables — wind-power density, heating/cooling-degree days, growing-degree days, and hydrology-derived variables — so a downscaled product can directly serve energy and agriculture consumers. |
| `urban_uhi`          | Urban heat-island-aware 50 m downscaling, diurnal-UHI-aware, built on European Urban Atlas and OSM land-use, targeting Cybele construction and Themis climate-adaptation products.                                                                             |

### 7.7 Climate-Emulator Model Design

Three Phase 175 design points define the emulator's stability and validation
requirements. The drift target (≤ 0.05 K decade⁻¹) is a hard requirement rather
than a best-effort target because an emulator that drifts on long rollouts is
useless for the decadal decision support that Cybele and Lakshmi need.

- **Emulator model (Phase 175.6.1.1).** A spherical-FNO / Ai2 ACE-class emulator
  trained on CMIP6 pre-industrial control plus SSP2-4.5 and SSP5-8.5 runs,
  producing stable 100-year rollouts at 1° resolution with closed
  energy/moisture budgets. The drift target is ≤ 0.05 K decade⁻¹ on global mean
  surface temperature.
- **Coupled feedbacks (Phase 175.6.1.2).** Sea-ice-albedo and soil-moisture
  feedback modules are coupled into the rollout via FNO-in-FNO composition, so
  ice-albedo and land-surface feedbacks evolve rather than being held fixed.
- **Hindcast validation (Phase 175.6.1.3).** A decadal hindcast driver
  (1960–2020) is validated against HadCRUT5, GISTEMP, and AMIP-SST before a
  checkpoint is promoted.

## 8. Operational Forecast Cycle (`@gaia/forecast-serving`)

The live operational cycle turns the latest analysis into published forecast
products. This section describes its cadence, gates, and rollback semantics —
all of which are enforced automatically by `@gaia/forecast-serving` without
manual intervention.

### 8.1 Cycle Cadence

Cycles run on the 00/06/12/18 UTC analysis schedule. A cycle launches when live
IFS HRES initial conditions are available, or fallback GFS initial conditions if
HRES is missing — the cycle is not skipped for want of the primary source. The
`gaia.forecast.cycle.started` event records which initial-condition source (HRES
or GFS) was used.

### 8.2 Publication Gate

A cycle's products may publish only when all three gates pass. The gates exist
to prevent bad model output from reaching downstream consumers. Any single gate
failure blocks the entire cycle from publishing.

1. **Rollout-stability diagnostics** — energy conservation, spectra, and blur
   checks (see §9.2).
2. **Skill-regression check** — a comparison against the prior promoted
   baseline. Failure fires `gaia.skill.regression_detected`.
3. **Data-completeness check** — a check on the cycle's inputs.

A cycle that fails any gate does not publish; its `ForecastProduct` records stay
in `publicationStatus: 'draft'`.

### 8.3 Cycle Rollback

When a published cycle is later found bad, it is rolled back: affected products
transition to `publicationStatus: 'rolled_back'`, a
`gaia.forecast.cycle.rolled_back` event fires, and downstream consumers fall
back to the previous good cycle. A rolled-back product is retained, not deleted,
so the failure is auditable.

### 8.4 Latency Target

The operational deterministic cycle has a p95 completion target under 20
minutes, measured from analysis availability to product publication, on the
planned H100-class cluster (Phase 175.8.1.5 names the H100-40 cluster). A
companion accuracy target: 24h deterministic RMSE within 2% of the published
GraphCast baseline.

### 8.5 Cycle State Sequence

The cycle runner moves through the following six stages in order. The stage
descriptions correspond to the Phase 175.8.1.2 design and the event set defined
in §11.

| Stage                   | Description                                                                 | Event emitted on entry                                                              |
| ----------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Initial-condition fetch | Fetch live HRES initial conditions, or fall back to GFS if HRES is missing. | —                                                                                   |
| Cycle started           | The forecast cycle launches.                                                | `gaia.forecast.cycle.started`                                                       |
| Forecast launch         | The forecast jobs run.                                                      | —                                                                                   |
| Publication gate        | The three publication gates are evaluated (§8.2).                           | `gaia.skill.regression_detected` on regression-check failure                        |
| Cycle completed         | All gates pass; products publish.                                           | `gaia.forecast.cycle.completed`, then `gaia.forecast.product.published` per product |
| Cycle rolled back       | A published cycle is later found bad.                                       | `gaia.forecast.cycle.rolled_back`                                                   |

## 9. Model Architecture (planned)

### 9.1 Deterministic Spherical GNN (`@gaia/graphcast`)

The GraphCast-Sovereign model follows the published GraphCast architecture
closely, with Oshun-specific training plans and stability diagnostics added on
top. The five design points below map to Phase 175 sub-tasks.

- **Icosahedral multi-mesh (Phase 175.2.1.1).** An icosahedral multi-mesh
  generator builds M0 (icosahedron) through M6 refinement (40,962 nodes), with
  uniform-area triangular faces and pre-computed edge lists for every refinement
  level.
- **Grid→Mesh encoder (Phase 175.2.1.2).** For each mesh node, the encoder
  attends to nearby 0.25° grid nodes (radius ≈ 0.6× mesh edge length) via an MLP
  on (node feature, grid feature, relative position) — identical semantics to
  the public GraphCast encoder.
- **Mesh processor (Phase 175.2.1.3).** 16 rounds of interaction-network message
  passing over all 6 refinement levels simultaneously (hierarchical multi-mesh),
  with residual updates and layer norm, producing updated mesh node embeddings.
- **Mesh→Grid decoder (Phase 175.2.1.4).** For each grid node, the decoder
  attends to its containing mesh triangle's 3 nodes via an MLP, predicts a
  residual tendency relative to the input state, and adds it to the input to
  produce the +6h forecast.
- **Autoregressive rollout (Phase 175.2.1.5).** A rollout driver applies the 6h
  step up to 40 times for a 10-day horizon, with checkpoint-supported gradient
  accumulation for multi-step fine-tuning.

### 9.2 Rollout-Stability Diagnostics (Phase 175.2.2.5)

Rollout-stability diagnostics are the first gate in the publication check
(§8.2). They guard against spectral collapse — a failure mode in autoregressive
GNN models where the spatial spectrum blurs or collapses over long rollouts,
making the forecast physically unrealistic even if per-step errors look
acceptable. Diagnostics cover energy conservation, the spectrum at T+240h versus
ERA5 climatology, and a blur detector, with automatic early-stop on spectral
collapse. These diagnostics also drive checkpoint rejection during training.

### 9.3 Probabilistic Diffusion (`@gaia/gencast`)

The ensemble model extends the deterministic backbone with a diffusion process
over residual states. This produces calibrated spread across ensemble members
rather than a single trajectory, which is required for any consumer that needs
to estimate tail risk rather than a best-guess outcome.

- **State-diffusion model (Phase 175.3.1.1).** A state-diffusion model operating
  on +12h residuals over the icosahedral mesh, with EDM parametrization
  (preconditioned score-matching, ρ=7, σ_min 0.02, σ_max 88), using the
  deterministic GNN as the score-network backbone, conditioned on the previous
  two states.
- **Samplers (Phase 175.3.1.2).** A deterministic DPM-Solver++ sampler for
  20-step ensemble generation, and a stochastic Heun sampler for calibrated tail
  sampling.
- **Operational ensemble (Phase 175.3.1.4).** 50-member operational ensemble
  generation on a single H100 / B200, with batched multi-seed sampling.

### 9.4 Training Plans

Four training plans are defined for the deterministic model. The base plan
establishes the initial checkpoint; the XL plan scales to 0.25° resolution; the
ensemble plan provides a cheap diversity baseline; and the loss-weighting plan
ensures physically important regions and variables are prioritized.

- **GraphCast-Sovereign base (Phase 175.2.2.1).** 37M parameters, 1° ERA5.
  Single-step MSE for 300k steps, then multi-step fine-tuning for 2, 4, … up to
  12 rollout steps with linearly decayed loss weights.
- **GraphCast-Sovereign-XL (Phase 175.2.2.2).** A 0.25°, 226M-parameter variant
  trained on a TPU-v5e / H100 pod.
- **Ensemble-of-Sovereigns (Phase 175.2.2.3).** 10 random seeds × different
  training subsets, a cheap deterministic ensemble baseline.
- **Loss weighting (Phase 175.2.2.4).** Per-variable, per-level, per-latitude
  loss weighting (area-weighted cos(lat) plus published GraphCast variable
  weights).

## 10. Data Architecture and Sampling (planned)

### 10.1 Storage and Catalog

The storage design is built around immutability and efficient access. Raw source
files are kept exactly as received from the provider; derived Zarr stores are
chunked for training locality. All products register STAC items so external
scientific users can discover them through standard geospatial catalog
interfaces.

- Raw source files are immutable and retained with source URL, provider,
  acquisition time, checksum, license, and access-policy metadata.
- ERA5 ingestion uses tiered MinIO storage: hot (last 5 years), warm
  (1990–present), cold (1940–1989).
- Derived Zarr stores are chunked by variable, level, time, and grid so training
  and serving jobs can read contiguous forecast windows efficiently. GRIB2→Zarr
  conversion uses `xarray + dask + kerchunk`.
- Zarr products preserve chunk layout, grid metadata, variable units, level
  metadata, the time axis, and the provenance hash.
- Forecast products are persisted as Zarr, GRIB-compatible export bundles, JSON
  summaries, STAC items, and OGC EDR/Features products where applicable.

### 10.2 Sampling and Splits

The sampling design ensures training data reflects the full distribution of
weather events, including rare extremes, and prevents any temporal leakage
between splits.

- **Stratified loader (Phase 175.1.3.2).** A stratified training-sample loader
  balances across seasons, basins, and extreme-event classes (from IBTrACS, SPI,
  and heat-wave indices), with a curriculum scheduler that up-weights tropical
  cyclones, atmospheric rivers, and blocking events.
- **Temporal split (Phase 175.1.3.3).** A rolling train/validation/test split
  enforces strict temporal separation — train ≤ 2018, validation 2019–2020, test
  2021+ — to prevent leakage through persistence and climate drift.
- **Statistics (Phase 175.1.3.4).** A statistics package computes per-variable,
  per-level mean, standard deviation, min, and max for model normalization and
  denormalization, stored versioned alongside each trained checkpoint. This is
  the source of `WeatherSample.normalizationVersion`.

## 11. Domain Events

Gaia publishes ten domain events. These events are the primary integration
surface for downstream consumers — rather than polling forecast endpoints,
consumers subscribe to the relevant events and react when products or alerts
arrive. Internal operations also use these events for monitoring, rollback
triggers, and model-promotion audit trails.

| Event                             | Trigger                                  | Payload                                                           |
| --------------------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| `gaia.data.ingested`              | A raw source archive is ingested         | Source class, acquisition time, checksum, object URI              |
| `gaia.dataset.versioned`          | A training dataset manifest is cut       | Dataset id, version, temporal split ranges, normalization version |
| `gaia.forecast.cycle.started`     | An operational cycle launches            | Cycle time, initial-condition source (HRES or GFS)                |
| `gaia.forecast.cycle.completed`   | A cycle passes all gates and publishes   | Cycle time, product ids, completion latency                       |
| `gaia.forecast.cycle.rolled_back` | A published cycle is rolled back         | Cycle time, affected product ids, rollback reason                 |
| `gaia.forecast.product.published` | A product becomes `published`            | Product id, model id/version, grid, variables, signed URIs        |
| `gaia.alert.cap.published`        | A CAP 1.2 alert is published             | CAP id, source forecast id, threshold rule, publication targets   |
| `gaia.skill.regression_detected`  | A skill-regression check fails           | Model id, baseline, regressed metric and magnitude                |
| `gaia.model.promoted`             | A checkpoint passes its evaluation gates | Model id, version, evaluation suite results                       |
| `gaia.model.rejected`             | A checkpoint fails an evaluation gate    | Model id, version, failed gate                                    |

Gaia consumes shared identity, tenant, storage, queue, model-serving,
observability, and evaluation contracts.

## 12. Forecast API Surface (`@gaia/forecast-serving`)

`@gaia/forecast-serving` exposes an OpenAPI 3.1 forecast API as a BFF, with
per-tenant rate limiting and signed-URL GRIB/Zarr output streaming. The ten
endpoints below cover the six forecast product types plus retrieval, download,
and alert operations.

| Method | Path                               | Purpose                                                |
| ------ | ---------------------------------- | ------------------------------------------------------ |
| `POST` | `/forecast/deterministic`          | Run a deterministic spherical-GNN forecast.            |
| `POST` | `/forecast/ensemble`               | Run a probabilistic diffusion ensemble forecast.       |
| `POST` | `/forecast/cyclone`                | Run a direct tropical-cyclone forecast.                |
| `POST` | `/forecast/nowcast`                | Run a minute-scale precipitation nowcast.              |
| `POST` | `/forecast/climate-scenario`       | Run a climate-emulation scenario rollout.              |
| `POST` | `/forecast/downscale`              | Downscale an existing forecast product to finer scale. |
| `GET`  | `/forecast/products/{id}`          | Retrieve a product's metadata and URIs.                |
| `GET`  | `/forecast/products/{id}/stac`     | Retrieve the product's STAC 1.0 item.                  |
| `GET`  | `/forecast/products/{id}/download` | Download the product files.                            |
| `POST` | `/forecast/alerts/cap`             | Publish a CAP 1.2-compatible severe-weather alert.     |

### 12.1 Mandatory Response Metadata

Every forecast response must include the model version, the cycle time, the
data-source versions used, an output hash, the skill summary where available,
and signed output URIs for large products. This requirement is non-negotiable: a
forecast a consumer cannot trace back to a model version and source set is not
publishable.

### 12.2 Product Warehouse and Catalogs

Products are persisted to a Zarr forecast-product warehouse on MinIO and
registered in a STAC 1.0 catalog, with OGC EDR and OGC API Features endpoints so
external scientific users can query products through standard geospatial
interfaces. The STAC catalog and OGC endpoints are the external-facing discovery
surface; the REST API is the primary interface for Oshun internal consumers.

### 12.3 CAP Alerts

`POST /forecast/alerts/cap` publishes CAP 1.2-compatible severe-weather alerts,
using the CAP 1.2 profile `urn:oid:2.49.0.1.840.1`. Each alert preserves the
source forecast identity and the threshold rule, integrates with Kuanyin
emergency escalation and Oshun shell-routine weather notifications, and emits a
`gaia.alert.cap.published` audit event for downstream publication.

## 13. Storage and Catalog Requirements

These requirements follow from the auditability and traceability goals of the
data architecture. Every stored artifact — raw data, derived Zarr, forecast
product, or CAP alert — must carry enough metadata to reconstruct its full
provenance chain.

- Raw data must retain the source provider, license/access policy, acquisition
  timestamp, checksum, and immutable object URI.
- Zarr products must preserve chunk layout, grid metadata, variable units, level
  metadata, the time axis, and the provenance hash.
- Forecast products must register STAC items and OGC EDR/Features metadata for
  external scientific users.
- CAP alerts must preserve the source forecast id, the threshold rule, the
  reviewer state if applicable, and downstream publication targets.

## 14. Evaluation Requirements

Model promotion is gated by per-model-class evaluation suites. A checkpoint that
passes its gates fires `gaia.model.promoted`; one that fails fires
`gaia.model.rejected` naming the failed gate. The gates are grounded in
community benchmarks so Gaia's published skill can be directly compared against
external frontier models.

- **Deterministic** — checkpoints must pass WeatherBench-2 and ECMWF-style
  scorecards, reproducing published 0.25° headline skill on Z500, T850, Q700,
  and U10.
- **Ensemble** — checkpoints must pass CRPS, reliability, rank histogram,
  spread-skill ratio, and tail-calibration gates, with CRPS validated against
  IFS-ENS (51 members, 9 km) and extreme-percentile verification (P99.9
  precipitation, heat-wave return periods, wind-speed tails) guarding against
  distributional shortcut learning.
- **Cyclone** — checkpoints must pass NHC-style track, intensity, formation,
  landfall, and ATCF product validation.
- **Nowcast** — checkpoints must pass CSI, POD, FAR, and FSS thresholds across
  precipitation rates (0.1/1/5/10 mm h⁻¹) and regions, versus Stage IV and
  radar-gauge blends.
- **Climate emulator** — checkpoints must pass energy, moisture, and drift
  budgets for long rollouts and validate the decadal hindcast against HadCRUT5,
  GISTEMP, and AMIP-SST.
- **Downscaling** — checkpoints must validate against HRRR, CERRA, and station
  residuals, and validate derived-variable calibration.
- **Stress tests** — promotion requires red-team cases: Hurricane Ian 2022,
  Hurricane Lee 2023, the 2021 Pacific Northwest heat dome, the February 2021
  Texas cold snap, and the July 2022 UK record heat. Every new checkpoint must
  match or exceed the prior on all cases before promotion.
- **Benchmarks and leaderboard** — a public-benchmark suite (WeatherBench-2,
  ECMWF scorecards, NHC verification, pysteps verification) publishes an
  automated leaderboard per checkpoint to the Phase 91 evaluation hub.

### 14.1 Data Flywheel

Evaluation results are not discarded after promotion gating — they feed back
into the training loop through the Phase 85 data flywheel. A `weather-outcomes`
collector extends this flywheel: forecast vs. analysis deltas per cycle, per
variable, per lead-time, and per region are fed back into training-data
difficulty weighting (Phase 89.4.1.8) and retraining triggers (Phase 85.17.1.5).
An observational feedback loop compares operational forecasts against surface
station, radiosonde, and buoy arrivals in real time and feeds per-station
residuals back into the station-bias correction heads.

## 15. Observability

Forecast availability, skill, latency, calibration, cycle completion, cost, and
downstream publication failure are first-class metrics with Grafana dashboards.
They are instrumented from the start, not derived after the fact. The SLO
dashboard tracks two specific targets: the p95 cycle-completion target (< 20
min, §8.4) and the 24h deterministic RMSE target (within 2% of the published
GraphCast baseline).

## 16. Downstream Contract Requirements

The contract boundary between Gaia and its consumers is the forecast product and
the CAP alert. Consumers must not read Gaia's internal training splits, must
preserve forecast uncertainty when surfacing results to users, and must carry
Gaia's provenance identifiers through their own audit trails.

- Asase, Demeter, Oya, Galatea, Cybele, Lakshmi, Uzume, Aphrodite, Veritas,
  Kuanyin, and Oshun integrations must consume public Gaia products or signed
  product URIs. They must not read Gaia private training splits directly.
- Downstream adapters must preserve forecast uncertainty and must not convert
  low-confidence forecasts into deterministic user actions without domain-owned
  policy gates.
- Severe-weather alert consumers must preserve CAP IDs and Gaia provenance in
  user-facing notifications and audit logs.

### 16.1 Downstream Consumers

Gaia owns forecast products; the consumer owns the decision made from them. The
table below maps each consumer to the Gaia outputs it reads and the decision it
owns independently.

| Consumer  | Gaia output consumed                                       | Consumer-owned decision                                  |
| --------- | ---------------------------------------------------------- | -------------------------------------------------------- |
| Asase     | Weather ensembles, GDD, precipitation, wind, heat, drought | Crop, irrigation, planting, and livestock operations     |
| Demeter   | Garden weather, GDD, frost, localized alerts               | Garden tasks, plant care, and automation rules           |
| Oya       | Wind, convective risk, cyclone tracks, no-fly weather      | Drone missions, no-fly cones, swarm safety               |
| Galatea   | Outdoor weather and hazard gates                           | Robot deployment and safety policies                     |
| Cybele    | Storm hardening, climate adaptation, urban downscaling     | Construction and property operations                     |
| Lakshmi   | Renewable energy potential and climate risk                | Trading, capacity planning, and personal finance outputs |
| Uzume     | Lightning, wind, precipitation, heat, outdoor-event risk   | Live-event safety decisions                              |
| Aphrodite | Outdoor shoot risk and severe-weather warnings             | Performer and production scheduling                      |
| Veritas   | Weather products and alert metadata                        | Editorial weather reporting and fact-checking            |
| Kuanyin   | Severe-weather emergency signals                           | Safety escalation and community protection               |

`@gaia/forecast-serving` also exposes renewable-energy potential (solar GHI,
wind 100m, hydro-inflow) to Lakshmi for energy trading and capacity planning.
Themis consumes the urban-downscaling product for climate-adaptation work.

## 17. Verification and Completion Criteria

Gaia is not complete until all of the following conditions are satisfied. Each
criterion has a corresponding automated check.

- Planned packages appear in the Nx project graph with `scope:gaia` tags.
- Data adapters have fixture-backed ingestion and checksum tests.
- The `WeatherSample` schema and the forecast product schemas have contract
  tests (the `WeatherSample` schema is Zod-validated and Zarr-chunk-aligned per
  Phase 175.1.3.1).
- Forecast APIs validate OpenAPI 3.1 output and generated clients.
- Model promotion is gated by deterministic, ensemble, cyclone, nowcast,
  climate, and downscaling evaluation suites.
- Downstream adapters have integration tests proving uncertainty, provenance,
  and alert IDs survive handoff.
- Observability dashboards cover cycle latency, skill, availability,
  calibration, cost, and rollback.

## 18. Research Release

The research-sovereignty plan publishes a scientific paper and an open-weights
release for each major model class, under Apache 2.0 where the model is derived
from openly-licensed pre-training data.

## 19. Source Coverage

This specifications document is a planned/design-level specification. Gaia has
no implemented code (`libs/gaia/*`, `apps/gaia/*`, and `services/gaia/*` do not
exist, and there are no `@gaia/*` path mappings in `tsconfig.base.json`). It was
written against `TODOS/phase-175.md`, `DOMAINS/gaia/features.md`, and
`DOMAINS/gaia/architecture.md`. Every schema, field, enum value, endpoint,
event, state, and design statement above traces to one of those three documents;
no items were invented to round out a section. The `SkillSummary` field list is
intentionally left unspecified because none of the three source documents
enumerate it. Task-level checkboxes remain in `TODOS/phase-175.md`.
