# Nyx Domain — Technical Specifications

> Technical specification for the Nyx cosmic-observatory platform: astronomical
> type system, REST and WebSocket APIs, database schema, data pipelines, the
> library and application surface, and configuration. Every entity, enum,
> endpoint, and table below is traceable to source in `libs/nyx/*` and
> `apps/nyx/*`.

---

This document is the authoritative technical reference for engineers working on
or integrating with the Nyx domain. It covers the exact TypeScript types, REST
endpoints, WebSocket message protocol, database schema, and environment
configuration — the level of detail needed to implement a new feature, add a
migration, or write client code against the API.

The sections follow a natural dependency order: the technology stack and
algorithm standards first (since everything else builds on them), then the type
system, then the API surface, then persistence, then the pipeline workers, and
finally the full library and application inventories.

---

## Technology Stack

The table below lists the key technology choice at each architectural layer.
Understanding this stack is essential before reading the sections that follow,
since the API, schema, and validation patterns all flow directly from these
choices.

| Layer              | Technology                                                             |
| ------------------ | ---------------------------------------------------------------------- |
| REST API framework | Hono + `@hono/zod-openapi` (`OpenAPIHono`), `@hono/swagger-ui`         |
| HTTP runtime       | `@hono/node-server`                                                    |
| Real-time          | WebSocket (`ws` library), custom Nyx WS server + update scheduler      |
| Database           | PostgreSQL, accessed via `pg`; schema as Zod models in `@nyx/database` |
| Migrations         | Knex migrations (`@nyx/database` `migrations/`)                        |
| Cache / rate limit | Redis (`ioredis`) for API rate-limit windows; in-process fallback      |
| Pipeline runtime   | Node.js workers (`pino` logging, `prom-client` metrics, `undici`)      |
| Ephemeris kernel   | `astronomy-engine` plus in-domain Meeus/VSOP-style implementations     |
| Web clients        | Vite + React + Three.js (`@react-three/fiber`, `drei`, `xr`)           |
| Validation         | Zod (catalog version) throughout                                       |
| Testing            | Vitest                                                                 |

> Note: the API uses raw `pg`, not Drizzle. `@nyx/database` defines schema as
> Zod object schemas and ships Knex migration files; there is no Drizzle ORM
> layer.

---

## Astronomical Algorithm Standards

The computation libraries in Nyx implement classical positional-astronomy
algorithms throughout, rather than delegating all calculation to an external
dependency. This is a deliberate choice: it makes the algorithms independently
testable against known-correct reference values and keeps the domain's accuracy
guarantees under its own control.

Ephemeris, coordinate, and event code implements Meeus-style analytic series,
Besselian-element eclipse prediction, SGP4 TLE propagation, and Kepler-equation
solvers. The domain additionally depends on the `astronomy-engine` package for
high-precision solar-system positions where the Meeus approximations reach their
accuracy limits. Coordinate types carry their reference frame and epoch
explicitly so equatorial, horizontal, galactic, and ecliptic frames cannot be
silently mixed.

---

## Core Type System (`@nyx/types`)

`@nyx/types` is the shared TypeScript type library that every other `@nyx/*`
package imports from. It exports six modules — `coordinates`, `celestial`,
`time`, `observer`, `orbital`, `visualization` — re-exported from
`src/index.ts`. All types below are exact as they appear in source.

### Coordinate Types (`coordinates.ts`)

The coordinate types encode all the information needed to identify a location in
the sky unambiguously — including the reference frame and epoch, which are
frequently omitted by naive implementations and lead to subtle cross-library
bugs.

**Angular unit aliases** (all `number`): `Degrees`, `Radians`, `Arcminutes`,
`Arcseconds`, `Hours`, `Milliarcseconds`.

**Sexagesimal representations** encode angles in base-60 notation as astronomers
traditionally record them:

- `SexagesimalAngle` — `degrees`, `minutes`, `seconds`, `sign` (`1 | -1`)
- `HMSAngle` — `hours`, `minutes`, `seconds`
- `DMSAngle` — `degrees`, `arcminutes`, `arcseconds`, `sign` (`1 | -1`)

**`CoordinateFrame`** union:
`ICRS | J2000 | B1950 | GALACTIC | ECLIPTIC | HORIZONTAL | SUPERGALACTIC`.

**`Epoch`** union: `'J2000.0' | 'B1950.0' | 'J2050.0' | number`.

**Coordinate interfaces:**

- `EquatorialCoordinates` — `ra` (Degrees, 0–360), `dec` (Degrees, −90..+90),
  optional `frame`, optional `epoch`
- `AstrometricCoordinates` extends `EquatorialCoordinates` — adds `pmRA`,
  `pmDec` (mas/yr), `parallax` (mas), `radialVelocity` (km/s),
  `observationEpoch` (Julian year)
- `HorizontalCoordinates` — `altitude`, `azimuth` (Degrees)
- `ApparentHorizontalCoordinates` extends it — `trueAltitude`, `refraction`
  (Arcminutes), `airMass`
- `GalacticCoordinates` — `l`, `b`; `SupergalacticCoordinates` — `sgl`, `sgb`
- `EclipticCoordinates` — `lambda`, `beta`, optional `epoch`
- `CartesianPosition` — `x`, `y`, `z`, `unit: DistanceUnit`
- `CartesianVelocity` — `vx`, `vy`, `vz`, `unit: VelocityUnit`
- `StateVector` — `position`, `velocity`, `frame`, `epoch`

**Unit unions:** `DistanceUnit` = `km | AU | pc | kpc | Mpc | ly | m`;
`VelocityUnit` = `km/s | AU/day | m/s | c`.

**Derived geometry:** `AngularSeparation` (degrees/arcminutes/arcseconds),
`PositionAngle` (degrees + `from`/`to`), `FieldOfView` (rectangular),
`CircularFieldOfView`, `GreatCircleArc`, `SphericalTriangle`.

**Transformation records:** `CoordinateTransformResult<T>` and
`CoordinateCorrection` whose `type` is one of
`precession | nutation | aberration | parallax | proper_motion | refraction | light_time`.

### Celestial Object Types (`celestial.ts`)

The celestial object types form a discriminated union that covers every class of
object in the domain's catalogs. Code that works generically on any catalog
query result uses `CelestialObject`; code that knows it is handling, say, an
exoplanet uses `Exoplanet` directly.

**`CelestialObjectType`** union (18 values): `star`, `double_star`,
`variable_star`, `planet`, `dwarf_planet`, `moon`, `asteroid`, `comet`,
`galaxy`, `nebula`, `cluster`, `quasar`, `pulsar`, `black_hole`,
`supernova_remnant`, `satellite`, `meteor_shower`, `unknown`.

**`CelestialObjectBase`** — `id`, `type`, `coordinates`
(`EquatorialCoordinates`), `catalogs` (`CatalogIdentifiers`), optional
`magnitudes`, optional `updatedAt`.

**`Magnitudes`** — optional photometric bands: `apparent`, `absolute`, `B`, `V`,
`R`, `I`, `J`, `H`, `K`, `U`, `G`.

**`ColorIndices`** — `BV`, `UB`, `VR`, `VI`, `JH`, `HK`, `BPRP`.

**`CatalogIdentifiers`** — cross-reference IDs: `hip`, `tyc`, `gaia`, `hd`,
`hr`, `sao`, `flamsteed`, `bayer`, `name`, `variable`, `ngc`, `ic`, `messier`,
`simbad`, `twoMass`.

**Stars** — `SpectralClassification` (`class` is one of
`O B A F G K M L T Y W C S`; optional `subclass`, `luminosityClass` of
`Ia Ib II III IV V VI VII`, `peculiarities`); `StellarProperties`
(`temperature`, `luminosity`, `mass`, `radius`, `surfaceGravity`, `metallicity`,
`age`, `rotationPeriod`); `Star` extends `CelestialObjectBase` with
`astrometry`, `spectralType`, `properties`, `colorIndices`, `distance`,
`constellation`.

**Double stars** — `DoubleStar` (`primary: Star`, `companions: StarCompanion[]`,
`separation`, `positionAngle`, `orbitalPeriod`, `isPhysical`); `StarCompanion`
(`designation`, `magnitude`, `spectralType`, `separation`, `positionAngle`).

**Variable stars** — `VariableStarType` =
`ECLIPSING | PULSATING | ERUPTIVE | ROTATING | X-RAY | OTHER`;
`VariableStarClass` =
`EA EB EW CEP DCEP RR DSCT MIRA SR NOVA SN FLARE GCAS MISC`; `VariableStar` with
`variableType`, `variableClass`, `maxMagnitude`, `minMagnitude`, `period`,
`epochMax`, `spectralType`, `gcvsName`.

**Solar-system bodies** — `SolarSystemBodyType` =
`planet | dwarf_planet | moon | asteroid | comet`; `SolarSystemBodyProperties`
(`mass`, `radius`, `density`, `surfaceGravity`, `escapeVelocity`,
`rotationPeriod`, `axialTilt`, `albedo`, `surfaceTemperature`, `atmosphere`);
`AtmosphericComposition` (`compound`/`percentage`); `Planet`, `Moon`, `Asteroid`
(`taxonomicClass`, `isPHA`, `orbitFamily`), `Comet` (`cometType` =
`periodic | non_periodic | great | sungrazer`), `DiscoveryInfo`.

**Exoplanets** — `ExoplanetDetectionMethod` =
`transit | radial_velocity | direct_imaging | microlensing | astrometry | timing | disk_kinematics`;
`Exoplanet` (typed as `type: 'planet'`) with `hostStar`, `detectionMethod`,
`mass`, `massIsMinimum`, `radius`, `orbitalPeriod`, `semiMajorAxis`,
`eccentricity`, `equilibriumTemp`, `inHabitableZone`, `distance`.

**Deep-sky objects** — `DeepSkyObjectType` =
`galaxy | nebula | cluster | quasar | pulsar | black_hole | supernova_remnant`;
`GalaxyMorphology` (Hubble sequence: `E0–E7`, `S0`, `Sa–Sd`, `SBa–SBd`, `Irr`,
`cD`, `BCD`, `dE`, `dSph`); `Galaxy`, `NebulaType`
(`emission | reflection | dark | planetary | supernova_remnant | protoplanetary`),
`Nebula`, `ClusterType` (`open | globular | association | moving_group`),
`StarCluster`.

**Search** — `CelestialSearchParams` (types, magnitude limits, cone-search
`center`/`radius`, `catalogId`, `namePattern`, `constellation`, `limit`,
`offset`); `CelestialSearchResult<T>` (`objects`, `totalCount`, `queryTimeMs`).
The discriminated union `CelestialObject` covers `Star`, `DoubleStar`,
`VariableStar`, `Planet`, `Moon`, `Asteroid`, `Comet`, `Exoplanet`, `Galaxy`,
`Nebula`, `StarCluster`.

### Time Types (`time.ts`)

These types cover every time representation used in astronomical computation.
Astronomical code uses multiple incompatible time scales, and making them
distinct types prevents mixing them accidentally.

Exports `JulianDate`, `ModifiedJulianDate`, `JulianDayNumber`, `JulianDateSpec`,
`TimeScale`, `TimeScaleConversion`, sidereal-time types (`SiderealHours`,
`SiderealDegrees`, `GreenwichSiderealTime`, `LocalSiderealTime`), epochs
(`StandardEpoch`, `BesselianYear`, `JulianYear`, `EpochSpec`), calendar types
(`GregorianDate`, `JulianCalendarDate`, `DateRange`), `DeltaT`/`DeltaTSource`,
`LeapSecond`, `TimeInterval`/`TimeUnit`, `TimeConstants`, phenomena types
(`RiseTransitSetTimes`, `TwilightTimes`, `MoonPhase`, `LunarPhaseInfo`),
`TimeZoneInfo`, and formatting types.

### Observer Types (`observer.ts`)

Observer types describe the physical context of a human observing session —
location, equipment, atmospheric conditions, and the session record itself.

Exports `GeographicCoordinates`, `ObserverLocation`, `Observer`,
`AtmosphericConditions`, `ObserverEquipment`, horizon types (`HorizonProfile`,
`HorizonPoint`, `HorizonCheckResult`), site types (`ObservingSite`,
`TelescopeInfo`, `TelescopeType`, `MountType`), visibility types
(`VisibilityAssessment`, `VisibilityFactor`, `SeasonalVisibility`), weather
types (`ObservingForecast`, `HourlyForecast`, `SkyBrightness`), and session
types (`ObservationSession`, `SessionTarget`).

### Orbital Types (`orbital.ts`)

Orbital types represent everything needed to describe and propagate an orbit:
element sets, state vectors, perturbation models, and the events that occur at
specific orbital configurations.

- `KeplerianElements` — `a`, `e`, `i`, `node` (Ω), `peri` (ω), `M`, `epoch`,
  optional `frame` (`ecliptic | equatorial`)
- `ExtendedOrbitalElements` — adds `longPeri`, `meanLong`, `q`, `Q`, `period`,
  `n`, `trueAnomaly`, `eccentricAnomaly`, `T`
- `TwoLineElement` / `TLELine1` / `TLELine2` — full parsed TLE structure
  (`classification` = `U | C | S`, BSTAR drag, mean motion, RAAN, etc.)
- `EphemerisPoint`, `EphemerisTable`, `EphemerisRequest` — `stepUnit` is
  `days | hours | minutes`; `frame` is
  `heliocentric | geocentric | topocentric`; `aberration` is
  `none | apparent | astrometric`
- `Perturbation` / `PerturbationType`
  (`gravitational | oblateness | solar_radiation | atmospheric_drag | relativistic | tidal`);
  `ElementType` = `osculating | mean`
- `OrbitalEventType` =
  `perihelion | aphelion | ascending_node | descending_node | opposition | conjunction | greatest_elongation | quadrature | stationary_point | eclipse | transit | occultation`;
  `OrbitalEvent`, `Conjunction`, `Opposition`
- `EclipseType` =
  `total_solar | partial_solar | annular_solar | hybrid_solar | total_lunar | partial_lunar | penumbral_lunar`;
  `Eclipse` (Besselian-style fields: contacts C1–C4, `gamma`, `sarosNumber`,
  `pathCoordinates`)
- `PlanetaryTransit`, `ExoplanetTransit`, `SatellitePass`, `SatelliteFlare`
- `OrbitDeterminationInput`, `OrbitDeterminationResult` (`orbitType` =
  `elliptic | parabolic | hyperbolic`)

### Visualization Types (`visualization.ts`)

Visualization types describe everything needed to configure and render a sky
chart — from the projection algorithm to individual renderable objects and their
display symbols.

Exports projection/grid types (`Projection`, `GridType`), chart configuration
(`SkyChartConfig`, `SkyChartDisplayOptions`, `SkyChartColors`), renderable
objects (`RenderableObject`, `RenderableStar`, `RenderableDeepSkyObject`,
`DSOSymbol`, `RenderablePlanet`), constellation render types
(`RenderableConstellation`, `ConstellationLine`, `ConstellationBoundary`),
interactivity types, animation types, 3D-scene types (`Scene3DConfig`,
`Camera3D`, `Lighting3D`, `Background3D`, `Scale3D`), export types, and
accessibility types.

---

## REST API (`apps/nyx/api`)

The API is an `OpenAPIHono` application. `apps/nyx/api/src/index.ts` boots an
HTTP server (`@hono/node-server`) and attaches the WebSocket server on the same
port. The static OpenAPI document is loaded from
`libs/openapi/docs/nyx/openapi.yaml` (validated as OpenAPI `3.1.0`, title
`Nyx Astronomy API`).

### Middleware Chain

The middleware chain is defined in `app.ts` and runs in this fixed order for
every request. Understanding the order matters when adding new middleware,
because earlier middleware can short-circuit later ones.

Applied in order: `secureHeaders` → `cors` (methods `GET`, `OPTIONS`) → `logger`
→ `timing` → `prettyJSON` → `requestId` → `onError(errorHandler)`. On `/api/*`
two further middlewares run after the above: `apiKeyAuth` then `rateLimit`.

### Authentication

API keys are passed via the `X-API-Key` header (the middleware also accepts
`Authorization: Bearer <key>` and an optional query parameter). Anonymous access
is allowed by default — a missing key yields tier `anonymous`. Keys must match
`^nyx_(free|pro|enterprise)_[a-zA-Z0-9]{6,32}$`.

There are two validator implementations, used in different environments:

- **Default validator**: an in-memory map of three demo keys
  (`nyx_free_demo123456`, `nyx_pro_testkey789`, `nyx_enterprise_corp456`) plus
  any keys supplied via `NYX_LOCAL_API_KEYS`. This is the validator that runs
  when no database URL is configured.
- **Production validator**: `createPgApiKeyValidator(databaseUrl)` performs a
  SHA-256-hashed lookup against the `api_keys` table, supporting revocation,
  expiry, and feature scoping.
- Tier feature sets: free → `basic, search, ephemeris`; pro → adds
  `events, satellites, batch`; enterprise → adds `webhook, priority`.
- Helpers: `getApiKeyInfo`, `hasFeature`, `requireTier`.

### Rate Limiting

Rate limits are enforced by a tiered fixed-window limiter (`rate-limit.ts`).
Redis is used when `NYX_REDIS_URL` or `REDIS_URL` is set; otherwise an
in-process store is used. The limits below are requests per minute with a burst
allowance:

`RATE_LIMIT_TIERS` (requests per minute / burst): `anonymous` 60/10, `free`
300/30, `pro` 1000/100, `enterprise` 5000/500. Responses expose
`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` headers.

### Error Format

All API errors follow RFC 7807 Problem Details (`application/problem+json`).
`ProblemDetailSchema` fields: `type` (URI), `title`, `status`, optional
`detail`, `instance`, `requestId`, `timestamp`, `code`. `ValidationErrorSchema`
extends it with a per-field `errors` array.

### Endpoint Surface

All routes are mounted under `/api/v1`. Success responses use the envelope
`{ success: true, data, [pagination], meta: { requestId, timestamp } }`.

**Service-info and health (unauthenticated):**

| Method | Path                   | Description                       |
| ------ | ---------------------- | --------------------------------- |
| GET    | `/`                    | Service metadata + endpoint index |
| GET    | `/health`              | Health check (`status`, `uptime`) |
| GET    | `/api/v1`              | API version + endpoint list       |
| GET    | `/api/v1/openapi.json` | Static OpenAPI document           |
| GET    | `/api/docs`            | Swagger UI                        |

**Objects (`routes/objects.ts`)** — catalog queries for celestial objects:

| Method | Path                     | Description                                  |
| ------ | ------------------------ | -------------------------------------------- |
| GET    | `/api/v1/objects`        | Paginated celestial-object list with filters |
| GET    | `/api/v1/objects/search` | Relevance-scored search by name/designation  |
| GET    | `/api/v1/objects/{id}`   | Single object by ID or catalog designation   |

`/objects` query params (`ObjectQueryParamsSchema` + `PaginationParamsSchema`):
`type` (`ObjectTypeSchema`), `catalog` (`CatalogSchema`), `constellation`,
`minMagnitude`, `maxMagnitude`, `page`, `limit` (max 100). `/objects/search`
params: `q` (required), `type`, `limit`. Search results carry `score` (0–1) and
`matchedField`.

**Ephemeris (`routes/ephemeris.ts`)** — position and visibility calculations for
celestial objects at a given time and location:

| Method | Path                         | Description                           |
| ------ | ---------------------------- | ------------------------------------- |
| GET    | `/api/v1/ephemeris`          | Position/visibility over a time range |
| GET    | `/api/v1/ephemeris/position` | Current position of an object         |
| GET    | `/api/v1/ephemeris/rise-set` | Rise / transit / set times for a date |

`/ephemeris` query (`EphemerisRequestSchema`): `objectId`, `startTime`,
`endTime`, `step` (default `1h`), `latitude`, `longitude`, `elevation`. The
response (`EphemerisResponseSchema`) contains `entries[]`
(`EphemerisEntrySchema`: `timestamp`, `jd`, `equatorial`, optional `horizontal`,
`ecliptic`, `distance` {`au`, `km`, `lightTime`}, `magnitude`, `phase`
{`illumination`, `angle`}, `angularSize`, `elongation`, `constellation`,
`isVisible`, `riseTime`, `transitTime`, `setTime`) and a `summary` block.

**Events (`routes/events.ts`)** — upcoming and past celestial events such as
eclipses, conjunctions, and meteor showers:

| Method | Path                      | Description                            |
| ------ | ------------------------- | -------------------------------------- |
| GET    | `/api/v1/events`          | Events in a date range with filters    |
| GET    | `/api/v1/events/upcoming` | Events in the next 7 days              |
| GET    | `/api/v1/events/types`    | Catalog of event types with categories |
| GET    | `/api/v1/events/{id}`     | Single event by ID                     |

`/events` query (`EventQueryParamsSchema` + pagination): `startDate`, `endDate`,
`type` (comma-separated), `types` (deprecated alias), `importance`, `latitude`,
`longitude`. Event categories returned by `/events/types`: `lunar`, `solar`,
`planetary`, `meteor`, `eclipse`, `seasonal`.

**Satellites (`routes/satellites.ts`)** — real-time satellite positions and pass
predictions:

| Method | Path                             | Description                        |
| ------ | -------------------------------- | ---------------------------------- |
| GET    | `/api/v1/satellites`             | Paginated satellite list           |
| GET    | `/api/v1/satellites/categories`  | Satellite category catalog         |
| GET    | `/api/v1/satellites/passes`      | Pass predictions for a location    |
| GET    | `/api/v1/satellites/{id}`        | Single satellite by NORAD ID       |
| GET    | `/api/v1/satellites/{id}/passes` | Pass predictions for one satellite |

`/satellites` query (`SatelliteQueryParamsSchema`): `category`
(`SatelliteCategorySchema`), `status` (`SatelliteStatusSchema`), `search`,
`minAltitude`, `maxAltitude`. `/satellites/passes` query
(`SatellitePassQueryParamsSchema`): `satelliteId` (default ISS), `latitude`,
`longitude` (required), `elevation`, `days` (max 14), `minElevation`,
`visibleOnly`.

### API Schema Enums

These enums are used across the request validation schemas and appear in the
OpenAPI document. They are the complete, canonical lists as of the current
source.

- `ObjectTypeSchema`:
  `star, planet, moon, asteroid, comet, galaxy, nebula, cluster, constellation, satellite, other`
- `CatalogSchema`:
  `messier, ngc, ic, hipparcos, tycho, gaia, sao, hd, yale, gcvs, mpc, jpl, norad, other`
- `EventTypeSchema` (33 values):
  `solar_eclipse, lunar_eclipse, planetary_conjunction, planetary_opposition, planetary_transit, greatest_elongation, meteor_shower, comet_perihelion, asteroid_close_approach, new_moon, first_quarter, full_moon, last_quarter, supermoon, lunar_occultation, solstice, equinox, perihelion, aphelion, iss_pass, satellite_flare, satellite_decay, variable_star_maximum, variable_star_minimum, nova, supernova, conjunction, opposition, occultation, transit, rise, set, meridian_transit, other`
- `EventVisibilitySchema`: `global, regional, local, not_visible`
- `EventImportanceSchema`: `major, moderate, minor`
- `SatelliteCategorySchema`:
  `space_station, starlink, weather, communication, navigation, earth_observation, scientific, military, amateur, debris, other`
- `SatelliteStatusSchema`: `active, inactive, decayed, unknown`

### API Data Sources

The API services back their responses with bundled-catalog libraries and sample
data rather than a live database read path. This is important to understand when
debugging: a query against `/api/v1/objects` does not hit PostgreSQL; it filters
an in-memory array.

- `objects-service` builds its catalog from `ALL_MESSIER_OBJECTS`
  (`@nyx/messier`) mapped into the API `CelestialObject` shape; queries filter
  that in-memory array.
- `events-service` serves a fixed in-module list of sample events
  (`ASTRONOMICAL_EVENTS`, ~11 entries for 2024).
- `satellites-service` uses `createSatelliteTracker` and `parseTLE` from
  `@nyx/realtime-satellites` with vendored TLE data for pass prediction.
- `ephemeris-service` orchestrates `@nyx/ephemeris` and `@nyx/constellations`.

The `celestial_events` table and `createPgApiKeyValidator` exist for production
deployments; the default request path is catalog/sample-data backed.

---

## WebSocket API (`apps/nyx/api/src/websocket`)

The WebSocket API provides real-time pushes for position updates, satellite
passes, and visibility alerts. It uses a custom server rather than a generic
pub/sub layer so that the subscription model (with per-tier budgets) and the
update scheduling logic can be kept fully under the domain's control.

A custom WebSocket server (`NyxWSServer`, created by `createNyxWSServer`) is
attached to the HTTP server at path `/ws` (configurable via `WS_PATH`). The
`setupNyxWebSocket` helper wires the server, registers position/satellite/alert
handlers, and starts an `UpdateScheduler`. Ping interval 30 s, ping timeout 10
s.

### Message Types (`NyxMessageType`)

The complete set of message types, grouped by purpose:

- **System:** `ping`, `pong`, `error`, `auth`, `auth_response`
- **Subscription:** `subscribe`, `unsubscribe`, `subscribed`, `unsubscribed`
- **Position:** `position:update`, `position:batch`
- **Satellite:** `satellite:position`, `satellite:pass:start`,
  `satellite:pass:update`, `satellite:pass:end`, `satellite:batch`
- **Alerts:** `alert:event`, `alert:pass`, `alert:visibility`, `alert:iss`
- **Time:** `time:sync`, `time:tick`

### Channels (`NyxChannel`)

Channels determine what data a client is subscribed to. The `{id}` and `{type}`
patterns allow narrowing to a specific object or event type.

`objects:*`, `objects:{id}`, `satellites:*`, `satellites:{id}`, `events:*`,
`events:{type}`, `passes:*`, `passes:{observer}`, `time:sync`.

### Message Envelope

All messages use the `NyxMessage<T>` envelope: `type`, optional `payload`,
`channel`, `id`, `timestamp`. Subscription messages carry options
`{ updateInterval, precision: 'low'|'medium'|'high', observer }`.

### Push Payloads

The payload type for each push message:

- `PositionUpdate` / `PositionBatch` — object positions with `equatorial`,
  optional `horizontal`, `distance`, `magnitude`, `phase`, `isVisible`
- `SatellitePosition` / `SatelliteBatch` — geodetic lat/lon/altitude, velocity,
  footprint, optional observer-relative `horizontal`/`range`
- `SatellitePassEvent` — `phase`
  (`start | rising | culmination | setting | end`), azimuth/elevation/range,
  `progress`
- `EventAlert`, `PassAlert`, `VisibilityAlert` — `AlertSeverity` is
  `info | notice | warning | urgent`
- `TimeSyncPayload`, `TimeTickPayload` — server time, Julian date, sidereal time

### Tier Limits (`DEFAULT_TIER_LIMITS`)

Per-tier `TierLimits` control how many concurrent connections and subscriptions
a client may hold, how frequently updates are sent, and how many messages per
minute the server will push. Higher tiers get higher frequencies — enterprise
gets 10 ms minimum intervals compared to 5000 ms for anonymous.

| Tier       | Connections | Subscriptions | Min interval | Msgs/min |
| ---------- | ----------- | ------------- | ------------ | -------- |
| anonymous  | 1           | 5             | 5000 ms      | 60       |
| free       | 3           | 20            | 1000 ms      | 300      |
| pro        | 10          | 100           | 100 ms       | 1000     |
| enterprise | 100         | 1000          | 10 ms        | 10000    |

The WS API-key validator in `index.ts` maps key prefixes to tiers (`nyx_pro_` →
pro, `nyx_ent_` → enterprise, other `nyx_` → free).

---

## Database Schema (`@nyx/database`)

`@nyx/database` exports Zod schema models (`src/schema/`) and Knex migration
files (`src/migrations/`, exported as the `migrations` namespace). PostgreSQL
extensions and enum types are created in the first migration.

### Migration Tables

The 9 migrations (`20260118000001`–`20260118000009`) create the ~36 tables
below. They are grouped here by the domain concern each group addresses.

- **Celestial objects:** `celestial_objects`, `stars`, `solar_system_objects`,
  `exoplanets`, `galaxies`, `nebulae`, `star_clusters`
- **Orbital mechanics:** `orbital_elements`, `tle_elements`,
  `ephemeris_entries`, `rise_transit_set_times`, `eclipse_data`,
  `celestial_events`, `lunar_phases`, `meteor_showers`, `perturbation_records`
- **Catalog cross-reference:** `catalog_metadata`, `catalog_cross_references`,
  `name_aliases`, `constellations`, `constellation_boundaries`,
  `constellation_star_patterns`, `artificial_satellites`
- **User data:** `observation_logs`, `saved_views`, `custom_annotations`,
  `tour_definitions`, `tour_stops`, `tour_progress`, `observing_lists`,
  `observing_list_items`, `user_preferences`, `achievement_definitions`,
  `user_achievements`, `user_statistics`
- **API:** `api_keys`

Migration 6 creates indexes; migration 7 seeds initial data; migration 8 adds a
uniqueness index used by the event-calculator pipeline; migration 9 creates
`api_keys` (PK `key_hash`, `tier` check constraint of `free`/`pro`/`enterprise`,
feature `text[]`, expiry/revocation columns).

### Schema Models — Celestial Objects (`schema/celestial-objects.ts`)

These Zod schemas are the authoritative definitions for each object type stored
in the database. Each model has a `Create…InputSchema` variant that omits `id`
and timestamp fields.

Common Zod schemas: `EquatorialCoordinatesSchema` (`ra` 0–360, `dec` −90..+90,
`epoch` default 2000.0, `frame` of `ICRS|FK5|FK4|galactic|ecliptic`),
`ProperMotionSchema`, `ParallaxSchema`, `RadialVelocitySchema`,
`MagnitudeSchema` (bands `V B U R I G BP RP J H K`), `CatalogIdentifiersSchema`
(20+ catalog IDs), `NameAliasesSchema`.

Object models — each with a `Create…InputSchema` (omits `id`/timestamps):

- `StarSchema` — `StarTypeSchema`
  (`main_sequence, giant, supergiant, hypergiant, subgiant, subdwarf, white_dwarf, neutron_star, brown_dwarf, red_dwarf, blue_dwarf, carbon_star, wolf_rayet, pulsar, magnetar, black_hole, unknown`),
  `SpectralClassificationSchema`, `StellarPropertiesSchema`,
  `VariableStarDataSchema` (`VariableTypeSchema` — 22 classes), and
  `MultipleStarDataSchema`
- `ExoplanetSchema` — `PlanetTypeSchema`
  (`terrestrial, super_earth, mini_neptune, neptune_like, gas_giant, hot_jupiter, hot_neptune, ice_giant, ocean_world, lava_world, dwarf_planet, unknown`),
  `DetectionMethodSchema` (10 methods), `PlanetaryOrbitSchema`,
  `PlanetaryPropertiesSchema`, `HabitabilityMetricsSchema` (`inHabitableZone`,
  `esi`, `phi`, `potentialWater`, `insolation`)
- `SolarSystemPlanetSchema` — physical and orbital properties, moon count,
  rings, atmosphere/surface composition records
- `GalaxySchema` — `GalaxyMorphologySchema` (Hubble sequence incl. `LIRG`,
  `ULIRG`), `GalaxyActivitySchema`
  (`normal, seyfert1, seyfert2, liner, quasar, blazar, radio_galaxy, starburst, agn, ulirg, unknown`),
  `GalaxyPropertiesSchema`
- `NebulaSchema` — `NebulaTypeSchema` (15 values incl. `hii_region`,
  `bok_globule`, `herbig_haro`), `NebulaPropertiesSchema`
- `StarClusterSchema` — `ClusterTypeSchema`
  (`open_cluster, globular_cluster, stellar_association, asterism, moving_group, embedded_cluster, unknown`),
  `TrumplerClassSchema`, `ClusterPropertiesSchema`
- `DeepSkyObjectSchema` — generic unified-query model with
  `DeepSkyObjectTypeSchema` (15 values)

Search schemas: `CoordinateSearchBoxSchema`, `ConeSearchSchema`,
`MagnitudeFilterSchema`, `PaginationSchema` (`limit` max 1000),
`SortOrderSchema`.

### Schema Models — Catalogs (`schema/catalogs.ts`)

Catalog schemas handle identifier cross-referencing — the logic of knowing that
HD 19445, HIP 14544, and Gaia DR3 2534784854721380352 all refer to the same
object.

- `CatalogTypeSchema` — ~55 catalog identifiers across star, variable-star,
  double-star, deep-sky, planetary, satellite, and database categories
  (`hipparcos`, `gaia_dr3`, `messier`, `ngc`, `ic`, `mpc`, `simbad`, `ned`,
  `norad`, …)
- `CatalogObjectTypeSchema` — 31 object classifications
- `DataQualitySchema` — `excellent, good, fair, poor, suspect, unknown`
- `CatalogMetadataSchema`, `CatalogIdentifierSchema`,
  `CatalogCrossReferenceSchema`, `NameAliasSchema` (`NameTypeSchema` — 16 name
  types including `arabic`, `chinese`, `indigenous`), `ConstellationInfoSchema`
- `ConstellationAbbreviationSchema` — all 88 three-letter IAU abbreviations
- Query schemas: `CatalogSearchQuerySchema`, `NameSearchQuerySchema`,
  `CrossReferenceQuerySchema`, `CrossReferenceResultSchema`

### Schema Models — Orbital Mechanics (`schema/orbital-mechanics.ts`)

`OrbitalBodyTypeSchema` (15 values incl. `centaur`, `tno`, `kuiper_belt`,
`oort_cloud`), `OrbitalReferenceFrameSchema` (`heliocentric_j2000`, `teme`,
`gcrf`, …), `KeplerianElementsSchema` with full element set and per-element
uncertainties.

### Schema Models — User Data (`schema/user-data.ts`)

User data schemas cover the full observing session lifecycle: site conditions
when the session was conducted, equipment used, per-object observation notes,
saved views and annotations, educational tour progress, and achievement records.

- **Observation logs** — `SeeingConditionsSchema`, `TransparencySchema`,
  `BortleScaleSchema` (1–9), `WeatherConditionsSchema`, `EquipmentTypeSchema`
  (11 types), `EquipmentUsedSchema`, `ObservationTypeSchema` (15 types),
  `ObservationLogSchema`
- **Saved views** — `ViewTypeSchema`, `ProjectionTypeSchema` (8 projections
  incl. `mollweide`, `aitoff`, `hammer`, `gnomonic`), `ViewConfigurationSchema`,
  `SavedViewSchema`
- **Annotations** — `AnnotationTypeSchema` (9 types), `AnnotationPointSchema`,
  `AnnotationStyleSchema`, `CustomAnnotationSchema`
- **Educational tours** — `TourDifficultySchema`
  (`beginner, intermediate, advanced, expert`), `TourCategorySchema` (15
  categories), `TourStepTypeSchema` (9 step types), `StepStatusSchema`,
  `TourStepDefinitionSchema`, `TourDefinitionSchema`, `TourProgressSchema`
- **Achievements** — `AchievementTypeSchema` (13 types),
  `AchievementDefinitionSchema` (rarity `common…legendary`),
  `UserAchievementSchema`
- **User preferences** — `UserPreferencesSchema` (default location, units,
  notification flags, theme incl. `night_vision`)
- **Observing lists** — `ObservingListItemSchema`, `ObservingListSchema`

---

## Data Pipelines (`apps/nyx/pipelines`)

The pipelines application contains four independent Node.js worker entry points.
Each pipeline runs on its own schedule and writes results to PostgreSQL for the
API to serve. They are intentionally kept separate so that a slow catalog sync
does not block satellite pass predictions, and a failing ephemeris generation
does not affect event calculations.

The four pipelines, registered in `PIPELINES` (`src/index.ts`):

| Pipeline              | Entry point              | Purpose                                                         |
| --------------------- | ------------------------ | --------------------------------------------------------------- |
| `tle-updater`         | `tle-updater.ts`         | Fetch TLE data, refresh satellite orbital state                 |
| `catalog-syncer`      | `catalog-syncer.ts`      | Sync Gaia/Hipparcos catalog data into object tables             |
| `ephemeris-generator` | `ephemeris-generator.ts` | Pre-compute solar-system positions                              |
| `event-calculator`    | `event-calculator.ts`    | Predict conjunctions/eclipses and persist to `celestial_events` |

The `event-calculator` works by walking pairs of visible bodies with the
`@nyx/events` conjunction scanner and Bessel-element eclipse predictors, then
upserting results into the `celestial_events` table via `pg`
(`INSERT INTO celestial_events …`). Each pipeline can run directly or as a
Kubernetes CronJob. Shared `common/` modules provide a `pino` logger, a
`prom-client` metrics registry, and an HTTP health server gated by
`METRICS_ENABLED`. Pipeline dependencies include `@nyx/gaia`, `@nyx/hipparcos`,
`@nyx/coordinates`, `@nyx/database`, `@nyx/ephemeris`, `@nyx/events`,
`@nyx/orbital`, `@nyx/realtime-satellites`, `@nyx/time`, and `astronomy-engine`.

---

## Library Surface (`libs/nyx/*`)

The Nyx domain ships **75 published packages** organised into nested groups.
Package names below are the canonical npm-style identifiers.

### Core / Foundation

| Package            | Path                   | Purpose                                          |
| ------------------ | ---------------------- | ------------------------------------------------ |
| `@nyx/types`       | `libs/nyx/types`       | Shared TypeScript type system (six modules)      |
| `@nyx/constants`   | `libs/nyx/constants`   | Physical and astronomical constants              |
| `@nyx/coordinates` | `libs/nyx/coordinates` | Coordinate-system transforms                     |
| `@nyx/time`        | `libs/nyx/time`        | Time scales, Julian date, sidereal time, Delta-T |
| `@nyx/utils`       | `libs/nyx/utils`       | Angular and magnitude math helpers               |
| `@nyx/database`    | `libs/nyx/database`    | Zod schema models + Knex migrations              |

### Astronomical Computation

| Package               | Path                      | Purpose                                                                                                                                                                    |
| --------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@nyx/ephemeris`      | `libs/nyx/ephemeris`      | Solar-system position computation                                                                                                                                          |
| `@nyx/orbital`        | `libs/nyx/orbital`        | Kepler/n-body, perturbations, Lagrange points, propagation                                                                                                                 |
| `@nyx/positional`     | `libs/nyx/positional`     | Topocentric correction, refraction, rise/set                                                                                                                               |
| `@nyx/events`         | `libs/nyx/events`         | Eclipse / conjunction / occultation / transit / meteor / aurora / satellite-pass / supermoon / deep-sky event prediction, plus calendar sync (largest computation library) |
| `@nyx/constellations` | `libs/nyx/constellations` | Multi-cultural constellation database + artwork                                                                                                                            |
| `@nyx/mythology`      | `libs/nyx/mythology`      | Constellation mythology data                                                                                                                                               |
| `@nyx/time-travel`    | `libs/nyx/time-travel`    | Historical/future sky reconstruction                                                                                                                                       |
| `@nyx/sky-clock`      | `libs/nyx/sky-clock`      | Sky-event materialiser, observer-location bucketing                                                                                                                        |

### Catalogs — Star Catalogs (`libs/nyx/catalogs`)

`@nyx/hipparcos`, `@nyx/gaia`, `@nyx/tycho`, `@nyx/bright-stars`, `@nyx/simbad`,
`@nyx/star-query` (multi-catalog unified query).

### Catalogs — Deep Sky (`libs/nyx/catalogs/deep-sky`)

`@nyx/messier`, `@nyx/ngc-ic`, `@nyx/nebulae`, `@nyx/clusters`, `@nyx/snr`,
`@nyx/pulsars`, `@nyx/neutron-stars`, `@nyx/black-holes`, `@nyx/quasars`,
`@nyx/gravitational-waves`. Plus `@nyx/supernovae`, `@nyx/ned`, `@nyx/sdss`.

### Catalogs — Exoplanets and Solar System

Exoplanets: `@nyx/nasa-exoplanets`, `@nyx/open-exoplanets`. Solar system
(`libs/nyx/catalogs/solar-system`): `@nyx/planets`, `@nyx/moons`,
`@nyx/horizons`, `@nyx/mpc`, `@nyx/neo`, `@nyx/comets`. Plus `@nyx/spacecraft`.

### Analysis (`libs/nyx/analysis`)

`@nyx/galaxy-classification`, `@nyx/habitability` (habitable zone, Earth
Similarity Index, atmospheric retention, tidal locking), `@nyx/visualization`
(chart generation).

### Real-Time (`libs/nyx/realtime`)

`@nyx/realtime-satellites` (TLE parsing + SGP4 satellite tracker),
`@nyx/realtime-solar`, `@nyx/realtime-neo`, `@nyx/realtime-events`.

### Renderer (`libs/nyx/renderer`)

12 WebGL/Three.js rendering packages: `@nyx/renderer-core`,
`@nyx/renderer-background`, `@nyx/renderer-stars` (published as
`@nyx/star-colors`), `@nyx/renderer-planets`, `@nyx/renderer-galaxies`,
`@nyx/renderer-nebulae`, `@nyx/renderer-clusters`, `@nyx/renderer-exotic`,
`@nyx/renderer-hdr`, `@nyx/renderer-lod`, `@nyx/renderer-scale`,
`@nyx/renderer-post-processing`.

### Widgets, Audio, Education, Integrations, Visualization

- **Widgets** (`libs/nyx/widgets`): `@nyx/widget-iss-tracker`,
  `@nyx/widget-moon-phase`, `@nyx/widget-star-map`
- **Audio** (`libs/nyx/audio`): `@nyx/audio-sonification`, `@nyx/audio-ambient`
- **Education** (`libs/nyx/education`): `@nyx/lesson-framework`,
  `@nyx/quiz-system`
- **Integrations** (`libs/nyx/integrations`): `@nyx/telescope`,
  `@nyx/stellarium`, `@nyx/planetarium`
- **Visualization** (`libs/nyx/visualization`): `@nyx/galaxy-distribution`

### Clients and Cross-Domain

| Package                   | Path                          | Purpose                                    |
| ------------------------- | ----------------------------- | ------------------------------------------ |
| `@nyx/client`             | `libs/nyx/client`             | Hand-written typed HTTP client for the API |
| `@nyx/api-client`         | `libs/nyx/api-client`         | Client with OpenAPI-generated types        |
| `@nyx/lilith-integration` | `libs/nyx/lilith-integration` | Cosmic-meditation content bridge to Lilith |

`libs/nyx/client-python` exists as a Python client package (`pyproject.toml`, no
TypeScript sources). `libs/nyx/docs` contains design documents
(`architecture.md`, `caching-strategy.md`) — it is not a published package.

### `@nyx/lilith-integration` — Cosmic Meditation Bridge

`@nyx/lilith-integration` does **not** handle Lilith domain events. It is a
content/state library: it provides curated meditation material derived from
cosmic imagery, which Lilith can consume without needing to understand
astronomical algorithms. Nothing in this package computes positions or queries
catalogs.

- `CosmicMeditationTheme` (8), `SessionIntensity`
  (`gentle | moderate | deep | transcendent`), `AwarenessPhase` (6 phases),
  `CosmicBreathPattern` (5)
- Content collections: `COSMIC_VISUALIZATIONS` (8 visualizations),
  `SPACE_PARALLELS` (8 inner/outer-space parallels), `CONSCIOUSNESS_EXERCISES`
  (6 exercises), `BREATH_PATTERNS`
- `LilithBridgeManager` — session lifecycle, breath-phase animation, exercise
  navigation; emits `LilithBridgeEventType` events (`session_started`,
  `phase_changed`, `breath_cycle_completed`, …)
- Zod schemas: `SessionStateSchema`, `UserPreferencesSchema`,
  `SessionConfigSchema`

---

## Application Surface (`apps/nyx/*`)

The Nyx domain ships **22 packaged applications**. Six are top-level apps; the
`education` directory holds 12 packaged apps and the `tools` directory holds 4.

### Top-Level Applications

| Application    | Package               | Path                      | Stack                     |
| -------------- | --------------------- | ------------------------- | ------------------------- |
| API            | `@nyx/api`            | `apps/nyx/api`            | Hono + OpenAPI + `ws`     |
| Star Map       | `@nyx/star-map`       | `apps/nyx/star-map`       | Vite + React + Three.js   |
| VR Planetarium | `@nyx/vr-planetarium` | `apps/nyx/vr-planetarium` | React-Three-Fiber + WebXR |
| AR Sky         | `@nyx/ar-sky`         | `apps/nyx/ar-sky`         | React-Three-Fiber + WebXR |
| Mobile         | `@nyx/mobile`         | `apps/nyx/mobile`         | React + `idb-keyval`      |
| Pipelines      | `@nyx/pipelines`      | `apps/nyx/pipelines`      | Node.js workers           |

`star-map` is the largest application (~189 source files) and contains feature
modules for navigation, planning, collections, constellations, spacecraft,
satellites, mythology, time-travel, filters, tonight-view, journal, audio,
meditation, solar, NEO, and bookmarks. `ar-sky` and `vr-planetarium` depend on
`@nyx/realtime-solar` and the React-Three ecosystem.

### Education Applications (`apps/nyx/education`)

- **Courses**: `@nyx/fundamentals-course`, `@nyx/solar-system-course`,
  `@nyx/stellar-course`, `@nyx/galactic-course`, `@nyx/cosmology-course`
- **Challenges**: `@nyx/star-identification`, `@nyx/orbital-mechanics`
- **Demos**: `@nyx/hr-diagram`, `@nyx/spectroscopy`, `@nyx/distance-ladder`,
  `@nyx/light-speed`, `@nyx/gravity-well`

### Tools Applications (`apps/nyx/tools`)

`@nyx/astrophotography`, `@nyx/light-curves`, `@nyx/observation-planner`,
`@nyx/orbit-determination`.

---

## Cross-Domain Integration

Nyx interoperates with the wider Oshun monorepo through
`@nyx/lilith-integration`, which surfaces cosmic-meditation content for the
Lilith consciousness domain. The `@nyx/orbital` library includes a
`kalika-relativity` module and a `solar-system-integration` module that provide
thin touchpoints with the Kalika mathematics/physics domain.

The boundary with Lilith is intentionally content-level rather than data-level:
Lilith receives pre-composed meditation material (visualizations, breath
patterns, awareness phases) rather than raw ephemeris data or catalog objects.
This prevents Lilith from taking an indirect dependency on the entire
astronomical computation stack.

> The earlier specification claimed Kafka event publishing via
> `@oshun/event-bus` and `nyx.*` event contracts in `@oshun/contracts`. No such
> Kafka publisher or `NyxEventTypes` contract exists in the current code; that
> claim has been removed.

---

## Configuration Reference

Configuration is supplied through environment variables read at process startup;
there is no single `NyxConfig` object in code. The variables below are the
complete set observed across the API and pipeline source.

| Variable                      | Component     | Effect                                              |
| ----------------------------- | ------------- | --------------------------------------------------- |
| `PORT` (default `3000`)       | API           | HTTP + WebSocket listen port                        |
| `HOST` (default `0.0.0.0`)    | API           | Bind address                                        |
| `WS_PATH` (default `/ws`)     | API           | WebSocket endpoint path                             |
| `NYX_REDIS_URL` / `REDIS_URL` | API           | Redis backing for the rate limiter                  |
| `NYX_LOCAL_API_KEYS`          | API           | Comma-separated `key:tier:owner` local key entries  |
| `NODE_ENV`                    | API/pipelines | Environment selection                               |
| `METRICS_ENABLED`             | Pipelines     | Set to `false` to disable the health/metrics server |

Tier rate limits, WebSocket tier limits, ping intervals, and pipeline registry
entries are defined as constants in code (`RATE_LIMIT_TIERS`,
`DEFAULT_TIER_LIMITS`, `PIPELINES`) rather than configuration.

---

## Acceptance Criteria

A change to the Nyx domain is acceptance-complete when all of the following
conditions hold. They are ordered to mirror the dependency chain: types first,
then API, then persistence, then tests.

1. All `@nyx/types` exports type-check and consumers compile against them.
2. New API routes are defined with `@hono/zod-openapi` `createRoute`, validate
   input with Zod schemas, and return the standard success/`ProblemDetail`
   envelopes.
3. New persistent entities are added both as Zod schema models in
   `@nyx/database` and as Knex migrations, with the migration's table covered by
   `migrations/20260118000006_create_indexes.ts` where query patterns need
   indexes.
4. Astronomical computations are unit-tested with Vitest against known-correct
   reference values (eclipse contacts, planet positions, separation angles), not
   merely shape assertions.
5. WebSocket message and channel additions extend `NyxMessageType` /
   `NyxChannel` and respect the per-tier `DEFAULT_TIER_LIMITS`.
6. Pipeline changes preserve the independent-worker model and the shared
   health/metrics server.
7. `npx tsc --noEmit`, `npx vitest run`, and lint pass for each affected
   package.

---

## Source Grounding

This specification was reconstructed from source: `libs/nyx/types/src/*`
(coordinates, celestial, time, observer, orbital, visualization, index),
`libs/nyx/database/src/schema/*` and `src/migrations/*`, `apps/nyx/api/src/*`
(app, index, routes, schemas, middleware, websocket),
`libs/nyx/events/src/index.ts`,
`libs/nyx/lilith-integration/src/lilith-integration.ts`,
`libs/nyx/constellations/src/index.ts`, `libs/nyx/sky-clock/src/*`,
`apps/nyx/pipelines/src/*`, the 75 `libs/nyx/**/package.json` files, the 22
`apps/nyx/**/package.json` files, `deploy/nyx/*`, and `TODOS/phase-21.md`. Every
enum value, field, endpoint, table, and package name is present in that source.
