# Nyx — Sky Events and Perspective

Nyx is V1's sky, awe, calendar, event, and cosmic-perspective domain — the place
a member opens to find out what is happening overhead tonight, whether it is
worth going outside, what it means, and how to keep a record of what they saw.
It serves the curious and the contemplative alike: a casual stargazer checking
the next meteor shower, an enthusiast logging an aurora through binoculars, or
someone who wants a Tara perspective meditation tied to the night sky. Nyx sits
among the six customer-facing domains (Tara, Arete, Veritas, Nyx, Nisaba, Metis)
hubbed at [../features.md](../features.md). It is unusual among them in carrying
a **genuine astronomical compute core** rather than a thin CRUD layer — see the
companion architecture deep-dive
[Customer Domains](../architecture/customer-domains.md).

This page is candid about what is real versus what is seam-mocked. The astronomy
engine and the contract model are unambiguously real, in-repo, and test-pinned
to known-correct published values. The live external data-source ingestion
(actual NASA/IMO/NOAA fetches), the OAuth two-way calendar-provider sync, and
cross-device observation sync are honestly **partial / boundary-mocked /
fail-loud**, not passed off as shipped. Where a thing is planned or
provider-gated, this page says so.

## What Nyx is, at a glance

Nyx covers five surfaces of one experience:

- **A computed "tonight" core.** A real Meeus-based ephemeris computes the Sun
  and Moon positions, the lunar illuminated fraction and phase, sidereal time,
  rise/transit/set, and observer-relative altitude/azimuth — entirely in-repo,
  with no external service dependency for the core figures.
- **A canonical event model.** Eleven event _families_ and twenty-seven event
  _types_ (meteor showers, eclipses, conjunctions, occultations, transits,
  comets, aurorae, satellite passes, supermoons, seasonal markers, deep-sky
  peaks), each with a family-specific detail schema, observation windows, and
  prediction-source provenance.
- **An observe-loop.** Forecasts say what _should_ be visible; the
  `LoggedObservation` contract lets a member record what they _actually_ saw —
  site, conditions, equipment, quality, attachments, sharing scope.
- **Calendar, save, follow, remind.** Per-event calendar entries with
  location-aware peak times, reminder cadences, and provider sync state.
- **Cross-domain companions.** Tara perspective practices, Nisaba cosmology
  overlays, Veritas grounded explainers, assistant evening narration, and Living
  Scenes "Sky Briefing" tonight-only briefings.

The canonical contracts live in `libs/contracts/src/nyx/index.ts`, re-exported
from the contracts package barrel via `export * from './nyx'`
(`libs/contracts/src/index.ts`, "Nyx V1 canonical contracts"). The compute and
adapter logic lives in `libs/oshun/domain-nyx/src`, the BFF route prefix is
`/v1/nyx/*` (`apps/oshun/bff/src/routes/nyx.ts`), and the web surfaces are under
`apps/oshun/web/src/app/nyx` and `.../app/domains/nyx`.

## Surfaces

### Consumer hub and first-class consumer routes

The consumer hub is at `/nyx` (the Lilith-design-system `NyxRoom`). The real web
tree, however, is richer than a single hub: alongside `/nyx` there are
first-class consumer routes that the older feature copy did not enumerate.
`apps/oshun/web/src/app/nyx/` contains, in addition to `page.tsx` (the room):

| Route              | Purpose                                                 |
| ------------------ | ------------------------------------------------------- |
| `/nyx/events`      | Upcoming sky-events list                                |
| `/nyx/observation` | Compact observation companion                           |
| `/nyx/sky-almanac` | The almanac surface driven by the depth modules         |
| `/nyx/tonight`     | The computed "tonight" view (Sun/Moon, phase, rise/set) |

These are consumer surfaces, not power-user tools, and they belong in any honest
description of the Nyx surface area.

### Power-user deep tools

The deep tools live under `apps/oshun/web/src/app/domains/nyx/`. The full set:
`analysis`, `catalogs`, `coordinates`, `events`, `events/[eventId]`, `learning`,
`moon`, `neo`, `observation-log`, `observation-log-deep`, `renderer`,
`satellites`, `sky-conditions`, `solar`, `solar-system`, `sonification`,
`star-chart`, `telescope`, `time-travel`, and `widgets`. The `[eventId]` dynamic
route is the per-event detail page; `observation-log` / `observation-log-deep`
back the observe-loop; `renderer` / `star-chart` / `solar-system` are visual
surfaces; `time-travel` and `sonification` are perspective tools.

### A note on mobile

Earlier architecture copy listed "mobile event cards" as a dedicated Nyx
surface. The code does not bear that out: `apps/oshun/mobile/src` has no `nyx/`
screen directory. Nyx appears on mobile only as **cross-domain companion
components** — for example `TaraNyxPerspectiveCompanionCard.tsx` — rather than
as a standalone Nyx mobile vertical. Treating "mobile event cards" as a shipped
Nyx surface tree is stale; the honest statement is that Nyx's mobile presence is
currently a companion card embedded in other domains.

## The ephemeris engine — the real astronomy core

The most impressive real asset in Nyx is
`libs/oshun/domain-nyx/src/ephemeris.ts` (~19.7 KB), a self-contained
astronomical computation module implementing the standard algorithms from Jean
Meeus, _Astronomical Algorithms_ (2nd ed.). Its own header is explicit about why
it exists: an audit found that `nyx/tonight` had been served by a **static
fixture** and the web layer **fabricated sky data on backend failure**. This
module computes the real positions instead, so the "tonight" surface is grounded
in arithmetic rather than a hand-built payload.

### Exported functions

| Symbol                                          | What it computes                                                        | Meeus reference   |
| ----------------------------------------------- | ----------------------------------------------------------------------- | ----------------- |
| `toJulianDay(date)`                             | Julian Day for a UTC instant                                            | ch. 7 (Gregorian) |
| `julianCenturies(jd)`                           | Julian centuries from epoch J2000.0                                     | —                 |
| `sunPosition(jd)`                               | Apparent geocentric solar position (RA/dec, ecliptic λ, distance in AU) | ch. 25, abridged  |
| `moonPosition(jd)`                              | Apparent geocentric lunar position (principal periodic terms)           | ch. 47            |
| `moonIllumination(jd)`                          | Phase angle, illuminated fraction, elongation, `phaseName`              | ch. 48            |
| `computeNightSky(date)`                         | Location-independent sky core (JD, Sun, Moon, and illumination)         | —                 |
| `greenwichMeanSiderealTime(jd)`                 | GMST in degrees                                                         | eq. 12.4          |
| `localSiderealTime(jd, longitudeEast)`          | LST for an east-positive longitude                                      | ch. 12            |
| `equatorialToHorizontal(pos, jd, lat, lonEast)` | RA/dec → altitude/azimuth (from north, eastward)                        | ch. 13            |
| `riseTransitSet(pos, date, lat, lonEast, h0)`   | Rise / transit / set plus circumpolar flags                             | ch. 15            |
| `computeTonight(date, location)`                | Full observer-relative "tonight" (alt/az and rise/set for Sun and Moon) | —                 |

The module also exports the `STANDARD_ALTITUDE` constant — the apparent
disk-center altitudes used for rise/set: `sun: -0.8333` (−0°50′, refraction plus
semidiameter), `moon: 0.125` (a mean value incorporating horizontal parallax),
and `star: -0.5667` (−0°34′ refraction only).

### Why it is built this way

A few design decisions are worth naming because they are deliberate and
correctness-bearing:

- **Longitude is east-positive throughout**, the standard geographic/API
  convention. Meeus's formulae are west-positive, so the observer-relative
  conversions invert the sign — a single consistent convention that the tests
  pin against Meeus's worked examples.
- **The lunar series keeps the principal (largest-amplitude) terms** — good to a
  few arc-minutes, "ample for a naked-eye 'tonight' surface." This is a
  documented approximation with its tradeoff written down, not a stub: the solar
  terms reproduce Meeus's worked example to ~0.001°.
- **`riseTransitSet` uses the sidereal rate, not 360°/day.** The code comment
  explains the subtlety: the target degree difference must be reduced mod 360
  _before_ dividing by `SIDEREAL_RATE = 360.98564736629`, because wrapping the
  day fraction instead would shift GMST by the rate and desync from the full
  GMST formula. It returns `alwaysUp` / `alwaysDown` flags for circumpolar and
  never-rising bodies.

### Known-answer tests (this is not a stub)

`libs/oshun/domain-nyx/src/ephemeris.test.ts` pins every routine to Meeus's
published worked examples, so any coefficient-transcription error fails loudly:

- `toJulianDay(2000-01-01T12:00Z)` is exactly **2451545.0** (the J2000.0 epoch),
  and Meeus example 7.a (Sputnik 1, 1957-10-04.81 UT) is **2436116.31**.
- `sunPosition` for 1992-10-13 reproduces ecliptic longitude **≈199.909°**,
  declination **≈−7.78507°**, distance **≈0.9976 AU** (Meeus example 25.b).
- `moonPosition` for 1992-04-12 reproduces ecliptic longitude **≈133.1627°**
  (within 0.02°) and distance **≈368409.7 km** (within 120 km) (example 47.a).
- `moonIllumination` for the same instant gives illuminated fraction **≈0.6786**
  (Meeus example 48.a), and reports `phaseName === 'new'`/`'full'` at known new
  and full moons.
- `greenwichMeanSiderealTime` matches example 12.a (**≈197.693195°**), and
  `equatorialToHorizontal` reproduces example 13.b (Venus from Washington:
  altitude ≈15.12°, azimuth ≈248.03° from north).
- `riseTransitSet` is self-consistency-tested: a stationary body must sit at
  `h0` at its own computed rise and set, and circumpolar/never-rising cases are
  asserted.

These are real domain-correctness tests against published values; they would
fail on random or hardcoded returns.

## The Sky Almanac depth modules

Under `libs/oshun/domain-nyx/src/depth/` there are **41 files** — twenty depth
modules, their tests, and the depth-page builder — that power the
`/domains/nyx/*` deep tools and the `/nyx/sky-almanac` surface. Each module is a
focused astronomical computation that composes the ephemeris core. They were
entirely unmentioned in the older feature copy.

| Module                                                            | Notable real exports                                                       |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `lunar-phase-calendar`                                            | `PRINCIPAL_PHASE_ELONGATION`, `findNextPhaseInstant`, `lunarPhaseCalendar` |
| `twilight-schedule`                                               | `TWILIGHT_ALTITUDE`, `TwilightWindow`, `twilightSchedule`                  |
| `twilight-phase-now`                                              | current twilight band for an observer                                      |
| `astronomical-night`                                              | `astronomicalNight` (the truly-dark window)                                |
| `equation-of-time`                                                | `equationOfTimeMinutes`, `equationOfTime`                                  |
| `solar-noon`                                                      | `solarNoon`                                                                |
| `solar-terms`                                                     | `SOLAR_TERMS`, `solarTerms` (the 24 traditional terms)                     |
| `solar-season-markers`                                            | equinox/solstice instants                                                  |
| `daylight-extremes`                                               | `daylightExtremes` (longest/shortest day)                                  |
| `zodiac-position`                                                 | `ZODIAC_SIGNS`, `zodiacPosition`, `sunZodiacPosition`                      |
| `planetary-hours`                                                 | `CHALDEAN_ORDER`, `WEEKDAY_RULER`, `planetaryHours`                        |
| `lunar-nodes`                                                     | `findNextLunarNode`, `lunarNodes`                                          |
| `moon-distance`                                                   | `MOON_MEAN_RADIUS_KM`, `apparentLunarDiameterArcmin`, `findNextLunarApsis` |
| `moon-phase-detail`                                               | detailed phase readout                                                     |
| `sun-distance`                                                    | Earth–Sun distance over time                                               |
| `chart-angles`                                                    | `meanObliquityDeg`, `chartAngles`                                          |
| `object-transit`                                                  | transit of an arbitrary catalog object                                     |
| `horizon-point` / `solar-horizon-points` / `lunar-horizon-points` | horizon azimuths for rise/set                                              |

The `depth-page` module (`depth-page.ts`) assembles these into a presentational
structure — `NyxDepthRow`, `NyxDepthSection`, `NyxDepthPage`, and the
`buildNyxSkyAlmanacPage` builder — which is what the almanac surface renders.
The existence of a dedicated `.test.ts` next to every module is the diagnostic
that these are real computations, not placeholders.

## The Nyx event and observation model

The event model is the richest contract in the repo. It is built from a
family/type taxonomy, per-family detail schemas, observation windows, prediction
sources, calendar sync, and the logged-observation loop — all with cross-field
`superRefine` validation enforcing domain correctness.

### Event taxonomy: 11 families, 27 types

`SkyEventFamilySchema` is an 11-value enum:

```
meteor-shower · eclipse · conjunction · occultation · transit · comet
aurora · satellite-pass · supermoon · seasonal-marker · deep-sky-peak
```

`SkyEventTypeSchema` refines this into 27 concrete types, including
`meteor-shower-peak`; the eclipse subtypes
`solar-eclipse-{partial,total,annular}` and
`lunar-eclipse-{partial,total,penumbral}`;
`conjunction-planet-{planet,moon,star}`; `lunar/planetary/asteroid-occultation`;
`mercury/venus/exoplanet-transit`; `comet-apparition`; `aurora-forecast`;
`iss-pass` and `bright-satellite-pass`; `supermoon`; the seasonal markers
`march-equinox`, `september-equinox`, `june-solstice`, `december-solstice`,
`cross-quarter`; and `deep-sky-peak`. A `getSkyEventFamily(type)` helper maps
every type to its family, and `SkyEvent`'s `superRefine` enforces that the
declared `family` matches the type's expected family.

### Per-family detail schemas

Each family carries its own detail block (`SkyEventDetailsSchema` is a record of
nullable per-family details, and `requiredDetailsKeyByFamily` enforces that the
matching block is present). These are far richer than a flat "type and peak
time" bullet:

| Family          | Detail schema                      | Representative real fields                                                                                                                          |
| --------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| meteor-shower   | `MeteorShowerEventDetailsSchema`   | `iauCode` (regex `/^[A-Z0-9]{3}$/`), `peakZenithalHourlyRate` (int 1–1000), `radiant` (an `AngularCoordinate`), `parentBody`, `activeStart/EndDate` |
| eclipse         | `EclipseEventDetailsSchema`        | `body` (solar/lunar), `subtype`, `sarosSeries`, `magnitude` (≤2), `gamma` (−2..2), `contactTimes` (first/second/maximum/third/fourth)               |
| conjunction     | `ConjunctionEventDetailsSchema`    | `angularSeparationDegrees` (0–10), `elongationFromSunDegrees`                                                                                       |
| occultation     | `OccultationEventDetailsSchema`    | `occultingBody`/`occultedBody`, `disappearance`/`reappearanceTimeUtc`, `maxDurationSeconds`                                                         |
| transit         | `TransitEventDetailsSchema`        | `kind` (mercury/venus/exoplanet), ingress/maximum/egress `contactTimes`                                                                             |
| comet           | `CometEventDetailsSchema`          | `designation`, `perihelionDate`, `predictedMagnitude`, `ephemerisObjectId`                                                                          |
| aurora          | `AuroraEventDetailsSchema`         | `hemispheres` (north/south), `kpIndexMin/Max` (0–9), `geomagneticStormLevel` (`g1`–`g5`), `validForecastHours` (≤168)                               |
| satellite-pass  | `SatellitePassEventDetailsSchema`  | `spacecraft` (iss/bright-satellite), `noradId`, `maxElevationDegrees`, `passStart/EndAt`                                                            |
| supermoon       | `SupermoonEventDetailsSchema`      | `perigeeDistanceKm` (≤500000), `illuminationPercent`, `fullMoonAt`                                                                                  |
| seasonal-marker | `SeasonalMarkerEventDetailsSchema` | `marker`, `hemisphereContext`, `traditionalName`                                                                                                    |
| deep-sky-peak   | `DeepSkyPeakEventDetailsSchema`    | `objectType` (galaxy/nebula/open-cluster/globular-cluster/asterism), `constellation`, `catalogRefs`, `recommendedApertureMm`                        |

Beyond the family-match rule, `validateSkyEventSubtypeDetails` enforces
finer-grained consistency: a `solar-eclipse-*` type requires
`eclipse.body === 'solar'`, an `iss-pass` requires
`satellitePass.spacecraft === 'iss'`, and a `*-equinox`/`*-solstice` type
requires the seasonal-marker's `marker` to equal the event type.

### The `SkyEvent` envelope

`SkyEventSchema` ties it together: `id`, `slug`, `type`, `family`, `title`,
`summary`, `peakTimeUtc`, `windowStartUtc`/`windowEndUtc`, `locationDependent`,
`visibilityScope` (`global`/`regional`/`local`/`not-visible`), `confidenceBand`
(`high`/`medium`/`low`/`stale`/`experimental`), an array of `bodies`
(`CelestialBodyRef`), `predictionSources`, `observationWindows`, the per-family
`details`, and related-content arrays linking the concept graph, Tara content,
and Nisaba content. Its `superRefine` enforces timestamp ordering
(`windowStart ≤ peak ≤ windowEnd`), uniqueness of body/source/window ids, window
`eventId` consistency, and that each window's `sourceRefIds` actually reference
the event's `predictionSources`.

### Observation windows and the quality band

`ObservationWindowSchema` is the per-region visibility forecast for an event. It
carries the `site` (latitude, longitude, elevation, timezone, region, geohash),
`startAt`/`peakAt`/`endAt`, a `qualityBand`
(`excellent`/`good`/`fair`/`poor`/`not-visible`), a `visibility` block (visible
flag, limiting reason, peak altitude/azimuth, apparent magnitude),
`recommendedEquipment`, and a `qualityInputs` block: `bortleClass` (1–9),
`astronomicalTwilight` band, `moonIlluminationPercent`, `cloudCoverPercent`,
`precipitationProbabilityPercent`, `windKph`, `seeing`, `transparency`, and
`weatherCondition`.

The quality band is not a free label — it is validated. The `superRefine`
enforces that not-visible windows set `visibility.visible === false` and carry a
`limitingReason`, that an invisible window must use the `not-visible` band, and
critically that an **`excellent` window requires dark enough skies**:
`bortleClass <= 4`, `cloudCoverPercent <= 25`, and an `astronomicalTwilight` of
`astronomical-twilight` or `night`. This is a real domain rule — you cannot
label a bright, cloudy, twilit sky "excellent."

```jsonc
// excerpt of an ObservationWindow's qualityInputs that would PASS as "excellent"
{
  "qualityBand": "excellent",
  "qualityInputs": {
    "bortleClass": 3, // ≤ 4 required
    "cloudCoverPercent": 10, // ≤ 25 required
    "astronomicalTwilight": "night", // must be astronomical-twilight or night
  },
}
```

### Prediction sources and the family-support matrix

`PredictionSourceKindSchema` has **11 kinds** — five more than older copy
listed. Each `PredictionSourceRef` carries `kind`, `displayName`, `uri`,
`version`, a `license` block (`label`/`uri`/`attribution`), a `freshness` block
(`issuedAt`/`validThrough`/`maxAgeHours`/`status` of
`current`/`stale`/`superseded`), a `confidenceContribution`, and a `fetchedAt` —
i.e. version, license, attribution, and freshness, exactly what a provenance
contract should carry. Its `superRefine` rejects a `validThrough` that is not
after `issuedAt`.

The 11 kinds and the families each may back (`predictionSourceFamilySupport`):

| Source kind                      | Backs families                                                                               |
| -------------------------------- | -------------------------------------------------------------------------------------------- |
| `nasa-jpl-horizons`              | eclipse, conjunction, occultation, transit, comet, supermoon, seasonal-marker, deep-sky-peak |
| `nasa-jpl-small-body-db`         | comet, occultation                                                                           |
| `imo-meteor-calendar`            | meteor-shower                                                                                |
| `iers-bulletin-a`                | eclipse, occultation, transit, seasonal-marker                                               |
| `noaa-swpc-geomagnetic-forecast` | aurora                                                                                       |
| `weather-provider`               | _(none — augments windows, never backs an event)_                                            |
| `bortle-light-pollution-atlas`   | _(none — augments windows)_                                                                  |
| `tle-provider`                   | satellite-pass                                                                               |
| `minor-planet-center`            | comet, occultation                                                                           |
| `usno-astronomical-applications` | eclipse, supermoon, seasonal-marker                                                          |
| `gaia-catalog`                   | occultation, deep-sky-peak                                                                   |

This matrix is enforced: `SkyEvent.superRefine` requires that **at least one**
prediction source supports the event's family
(`predictionSourceSupportsEventType`), so an aurora event with only a weather
provider attached is a validation error. The five sources older copy omitted are
`nasa-jpl-small-body-db`, `tle-provider`, `minor-planet-center`,
`usno-astronomical-applications`, and `gaia-catalog`.

### The observe-loop: `LoggedObservation`

Forecasts predict; `LoggedObservationSchema` records reality. Its module comment
states the intent plainly: "SkyEvent is the predicted phenomenon.
ObservationWindow is the visibility forecast for a region. LoggedObservation
closes the loop: the user reports what they actually saw … so that future
forecasts can incorporate ground-truth and so the user's lifetime observation
journal stays auditable."

A `LoggedObservation` carries `eventId` (nullable — you can log an unscheduled
sight), `eventType`, `userId`, `observedAt`, `durationMinutes`, the `site`, a
`skyCondition` (`LoggedObservationSkyConditionSchema`: bortle, seeing,
transparency, weather, cloud %, moon-phase %), `equipment`
(`LoggedObservationEquipmentSchema`: instrument kind, aperture, magnification,
exposure, notes), a `qualityRating` (the same quality band), a
`visibilityScope`, free-text `notes`, `attachments`
(photo/sketch/audio-note/video), a `sharedScope` of
`private`/`household`/`public`, and a `sourceClient`
(`mobile-app`/`web-app`/`desktop-app`/`api`). Its `superRefine` even catches an
inconsistency: a `not-visible` rating with clear-sky cloud cover (`< 20%`) is
rejected.

This contract is wired to the web observe-loop surfaces
(`/domains/nyx/observation-log` and `observation-log-deep`, backed by
`NyxObservationLog.tsx`). Per the run notes (`v1-real-infra-run-2026-06-22.md`
§8), local persistence is done via `useNyxStore` over
`localStorage['oshun.nyx']`, rendering rows tagged with
`data-nyx-observation-event-id`. What remains is the honest gap below.

### Calendar integration: providers, cadences, sync state

`CalendarProviderSchema` is `['google', 'apple', 'outlook', 'ics-file']` — note
the **`ics-file`** provider, which older copy omitted. It carries its own
special case: the `CalendarSyncEntry.superRefine` requires `externalCalendarId`
for every provider _except_ `ics-file` (an ICS download has no external calendar
id). `ReminderCadenceSchema` is
`['peak-only', 'evening-of', 'week-before', 'none']`.

A `CalendarSyncEntry` models the per-event calendar row: provider,
external/provider event ids, a `syncState`
(`pending`/`synced`/`conflict`/`disabled`/`deleted`), location-aware peak time
(`peakTimeUtc`, `peakTimeLocal`, and an `ObservationSite` location), a
`syncDirection` (`oshun-to-provider`/`provider-to-oshun`/`two-way`), reminders,
and conflict reason. Its `superRefine` enforces that a `synced` entry has a
`providerEventId` and `lastSyncedAt`, that a `conflict` entry has a
`conflictReason`, and that `reminderCadence === 'none'` cannot carry reminders
(and a non-none cadence must carry at least one). Each `CalendarReminder` has a
`cadence` (excluding `none`), a `channel`
(`push`/`email`/`sms`/`external-calendar`), an `offsetMinutesBeforePeak`, and an
`enabled` flag.

## How the data flows

The BFF exposes the Nyx read/launch surface under `/v1/nyx/*`
(`apps/oshun/bff/src/routes/nyx.ts`):

- The domain-adapter family at `/v1/nyx/adapter/*`: `capabilities`,
  `availability`, `home-cards`, `continue-items`, `search`, `launch`,
  `nightly-highlights`, `object-search`, `saved-objects`, `event-reminders`,
  `observation-logs`, and `bridge-moments`.
- `/v1/nyx/event-actions` for save/follow/remind state, and
  `/v1/nyx/perspective/home` for the home perspective section.

Supporting modules sit beside the route (note these live under
`apps/oshun/bff/src/` subdirectories, not all under `routes/`): the per-user
store `apps/oshun/bff/src/nyx/nyx-member-stores.ts`, the reminder delivery
bridge `apps/oshun/bff/src/reminders/nyx-reminder-bridge.ts`, the read adapters
`apps/oshun/bff/src/adapters/nyx-read-adapters.ts`, and the 3D/scene route
`apps/oshun/bff/src/isis/nyx-3d-route.ts`.

The reminder bridge is a good example of honest scoping. Its header makes clear
it **does not create reminders** — Nyx's own subsystem does that
(`getEventReminders`/`setEventReminder`/`toggleEventReminder`). The bridge
_delivers_ the ones a user already opted into, through the BFF's in-app reminder
pipeline. Delivery is deduped by a stable per-(user, reminder) session id
(`nyx-event:${userId}:${reminder.reminderId}`), so re-emitting every tick is a
no-op. It is fail-soft per user: a Nyx read that throws skips that user and
never aborts the sweep. In-app delivery is explicitly _additive_ to Nyx's own
push/email/sms/calendar channels.

A separate, sizeable `libs/nyx` library tree (~30 subdirectories incl.
`catalogs/`, `constellations/`, `coordinates/`, `orbital/`, `positional/`,
`realtime/`, `renderer/`, `sky-clock/`, `time-travel/`, and `client-python/`)
holds deeper astronomy machinery. Its `libs/nyx/ephemeris` package (`cache`,
`generator`, `minor-bodies`, `visibility`) is the `@nyx` ephemeris consumed
elsewhere in the platform — e.g. V9's
`libs/v9/hephaestus/src/nyx-sky-explorable.ts` and
`libs/v9/aletheia/src/kernel-evaluators.ts`.

## Cross-domain companions

Nyx is a hub for cosmic perspective, so it leans on its neighbors:

- **Tara perspective recommendations** — awe practices, perspective meditations,
  and night-sky-themed reflections (the mobile `TaraNyxPerspectiveCompanionCard`
  is the embedded surface). See
  [Tara — Rituals and Contemplative Practice](./domain-tara.md).
- **Nisaba cosmology overlays** — cultural cosmology references and
  cross-tradition mappings (e.g. the Pleiades across cultures), via the
  `nisaba-cosmology-overlay` module. See
  [Nisaba — Scholarly Study](./domain-nisaba.md).
- **Veritas explainers** — grounded explanations of phenomena with source-set
  citations and retracted-source handling. See
  [Veritas — Grounded Stories and Claims](./domain-veritas.md).
- **Assistant explainers** — voice-mode evening narration for tonight's sky with
  location-aware visibility framing and accessibility audio description. See
  [Assistant Experience](./assistant-experience.md).
- **Concept-graph linking** — phenomena ↔ named events ↔ teacher references ↔
  practice prescriptions, threaded through the `relatedConceptGraphNodeIds`,
  `relatedTaraContentIds`, and `relatedNisabaContentIds` arrays on `SkyEvent`.
- **Living Scenes integration** — Nyx ships **Sky Briefing** Living Scenes:
  tonight-only briefings tied to the celestial-event clock, with
  Northern/Southern hemisphere parity. The score's camera/visual focus advances
  across visible objects as the narrator names them, time-aligned to ±60 ms.
  Observer location is opt-in per session; the briefing materializes once per
  location-bucket per night. See
  [Living Scenes — Concept and Customer Promise](./living-scenes-overview.md).

## Real vs. planned — the honest state

This matters, and the docs are candid about it elsewhere; this page keeps that
candor.

**Real, in-repo, test-pinned:**

- The ephemeris engine and its 41 depth modules — genuine Meeus-based
  computation with known-answer tests against published values. Not a stub by
  any diagnostic.
- The contract model — the richest in the repo, with cross-field `superRefine`
  validation enforcing real astronomical and provenance rules.
- Local observation persistence — `useNyxStore` over
  `localStorage['oshun.nyx']`.

**Partial / boundary-mocked / fail-loud (per `V1/AUDIT_2026-06-24.md`):**

- `nyx-event-calendar-sync-reminder` is **partial**: two-way provider sync is
  **mocked at the boundary**, and reminder dispatch/delivery plus the
  observation-log back-link remain. Live calendar-provider OAuth sync is not
  fully wired.
- `nyx-tonight-observation` is **deep** as of 2026-07-02: the browser journey
  now drives event-detail → observation-log handoff, local-first save,
  `/v1/nyx/observations` POST/GET read-back after local storage clear, Home Nyx
  support-card and current-week KPI increment, and the observation-log versus
  telescope-control equipment-profile boundary.
- `nyx-to-tara-bridge` is **URL-only telemetry** at present.
- Live external data-source ingestion — the actual NASA JPL / IMO / NOAA SWPC
  fetches — is **seam-mocked / fail-loud** rather than fully wired. The
  contracts model the provenance precisely; the live fetchers are planned work,
  not claimed as shipped.

In short: a real astronomy core and a real, validated contract model, with the
external integrations honestly marked partial or planned. See
[Customer Domains](../architecture/customer-domains.md) for the architectural
framing and [../features.md](../features.md) for the feature hub.

## Related

- [Customer Domains](../architecture/customer-domains.md) — architectural
  deep-dive for the six verticals, including Nyx
- [Tara — Rituals and Contemplative Practice](./domain-tara.md) — perspective
  and awe companions
- [Nisaba — Scholarly Study](./domain-nisaba.md) — cosmology overlays and
  cross-tradition mappings
- [Veritas — Grounded Stories and Claims](./domain-veritas.md) — grounded
  explainers for phenomena
- [Living Scenes — Concept and Customer Promise](./living-scenes-overview.md) —
  the Sky Briefing template
- [Assistant Experience](./assistant-experience.md) — evening sky narration
- [Product Surfaces](./product-surfaces.md) — where Nyx sits in the surface map
- [Subsystem Glossary](./glossary.md) — terminology
