---
status: reconciled-partial
coverage_depth: representative
last_reconciled: 2026-07-18
specs:
  - apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts
source: WALKTHROUGH/results/external-dependencies/README.md
---

# Dependency result: Nyx logged-observation contract

- **Opened**: 2026-05-29 to distinguish a user’s observed event from the
  existing forecast-window contract and to record the missing canonical model.
- **Reconciled**: 2026-07-18 against `LoggedObservationSchema`, persistence
  registration, generated Prisma, the shipped migration, the dual-write adapter,
  and its live-DB test. This was a source reconciliation, not a fresh production
  walk.
- **Verdict**: **partial** — the canonical contract and initial persistence path
  now exist, so the old “missing model” decision is resolved. The adapter fills
  unavailable site and sky fields with neutral placeholders, maps free-text
  labels into `event_type`, reads the legacy table, and never validates the
  inserted projection through `LoggedObservationSchema`.
- **Primary spec**: `apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts`.

## Result at a glance

| Evidence lane      | Current result                                                                 | Authority limit                                                             |
| ------------------ | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Contract design    | User, event, site, sky, quality, equipment, attachments, sharing, source ship  | Does not model group observers; product decision is single `userId`         |
| Persistence model  | Registry, Prisma model, migration, indexes, and tombstones ship                | Repository presence; production rollout not inspected                       |
| Creation write     | Observation creation inserts legacy and canonical rows                         | Sequential inserts with no transaction or repair receipt                    |
| Initial mapping    | Conditions map to cloud cover and quality; unknown site is an explicit neutral | `0,0`, UTC, and `unspecified` are placeholders, not observed provenance     |
| Automated evidence | Live-DB test checks canonical row, condition projection, and web source client | It does not parse the row or prove every condition/attachment/equipment     |
| Read/corroboration | Observation listing still reads the legacy row                                 | No canonical read, forecast match, attachment lifecycle, sharing, or delete |

## Evidence map

The implemented projection closes the storage gap while leaving provenance and
canonical authority unresolved.

```mermaid
flowchart LR
    A[Log observation form] --> B[Legacy observation row]
    B --> C[Project condition and neutral site]
    C --> D[v1_nyx_logged_observation]
    D -. reads still legacy .-> E[Observation journal]
    D -. no connected match .-> F[Observation window corroboration]
    D -. no connected flow .-> G[Attachments and public sharing]
    B -. no atomic rollback .-> H[Possible split write]
```

## Proven observations

### Observation now has a distinct canonical type

- `LoggedObservationSchema` is separate from `ObservationWindowSchema`. It
  models what one user reports seeing, when and where, with sky conditions,
  quality, equipment, attachments, visibility, share scope, source client, and
  timestamps.
- The persistence registry registers the contract and the generated schema maps
  it to `v1_nyx_logged_observation`. Migration
  `20260528022916_nyx_logged_observation_and_library_collection` creates the
  table plus canonical identity, tenant, payload, live-scan, and tombstone
  indexes.
- These artifacts retire the old instruction to design and register a new Nyx
  contract; they do not, by themselves, make it read authority.

### Creation writes a deliberately sparse projection

- `observations.add()` stores the submitted event id, label, time, conditions,
  note, and owner in the legacy row, then constructs a canonical peer. Clear,
  partial, and overcast conditions map to cloud-cover estimates and quality
  bands; attachments and equipment start empty or null; sharing starts private.
- Because the form does not collect a site, the canonical row uses latitude and
  longitude zero, elevation zero, UTC, and region `unspecified`. Those values
  communicate missing context only if consumers treat them as placeholders.
- The live-DB test proves a clear observation produces a canonical row with
  zero-percent cloud cover, `good` quality, and `web-app` source.

## Boundaries and gaps

- **A placeholder site is not observation provenance.** Coordinates `0,0` are a
  real place on Earth as well as a common sentinel. Without an explicit unknown
  flag, downstream geographic analysis could mistake missing context for Gulf of
  Guinea evidence.
- **Free text is not a valid event taxonomy.** The adapter inserts `targetLabel`
  into `event_type`, while the contract expects `SkyEventTypeSchema`. The
  focused test uses “Perseid meteor shower peak” and never proves that
  reconstructed data passes the enum.
- **A canonical table row is not a contract-valid object.** Adapter-generated
  ids and owner/event ids can be non-UUID while the Zod contract requires UUIDs;
  SQL column assertions do not detect that mismatch.
- **A dual-write is not atomic.** Legacy and canonical inserts are separate
  queries with no transaction, outbox, or automated reconciliation path.
- **A write projection is not canonical read authority.** The journal list still
  reads `goal3_stub_observation`, and there is no canonical update, attachment,
  share-scope, tombstone, or forecast-corroboration path.
- **Contract source is not production deployment proof.** No production
  migration ledger, real observation row, or downstream forecast consumer was
  inspected in this reconciliation.

## Re-run evidence

Run the focused live-DB assertion with the repository Postgres test setup:

```bash
pnpm exec vitest run \
  apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts \
  -t "observations.add\(\) dual-writes"
```

A green result proves one initial condition mapping and canonical insert. It
must not be summarized as contract validation, trustworthy geolocation,
canonical journal reads, or observation-window corroboration.

## Source trail

- [External-dependency index](./README.md)
- [Nyx contract](../../../libs/contracts/src/nyx/index.ts)
- [Persistence registry](../../../libs/oshun/persistence/src/contract-persistence-registry.ts)
- [Generated Prisma schema](../../../libs/oshun/persistence/prisma/schema.prisma)
- [Canonical migration](../../../libs/oshun/persistence/prisma/migrations/20260528022916_nyx_logged_observation_and_library_collection/migration.sql)
- [Postgres adapter](../../../apps/oshun/bff/src/routes/domain-stubs-postgres.ts)
- [Live-DB adapter test](../../../apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts)

## Cross-references

- [Nyx tonight-observation journey](../../journeys/nyx-tonight-observation.md)
- [Nyx observation view](../../customer/07-nyx/nyx-observation.md)
- [Nyx calendar/reminder journey](../../journeys/nyx-event-calendar-sync-reminder.md)
- [Library collection contract result](./library-collection-contract.md)
- [External-dependency convention](./README.md)

## Open questions

- How will missing site be represented without overloading valid coordinates,
  and when will the product collect location, timezone, and consent?
- What explicit mapping converts form target labels and optional object ids into
  `SkyEventTypeSchema` plus catalog identity?
- Which boundary reconstructs the stored row and requires
  `LoggedObservationSchema.parse` before it is accepted as canonical?
- Will the paired writes become transactional or outbox-backed, and what
  reconciliation detects historical split rows?
- Which service owns canonical reads, attachments, forecast corroboration,
  privacy changes, and deletion/tombstone behavior?
