- 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 throughLoggedObservationSchema. - 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.
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#
LoggedObservationSchemais separate fromObservationWindowSchema. 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. Migration20260528022916_nyx_logged_observation_and_library_collectioncreates 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,
goodquality, andweb-appsource.
Boundaries and gaps#
- A placeholder site is not observation provenance. Coordinates
0,0are 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
targetLabelintoevent_type, while the contract expectsSkyEventTypeSchema. 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
- Nyx contract
- Persistence registry
- Generated Prisma schema
- Canonical migration
- Postgres adapter
- Live-DB adapter test
Cross-references#
- Nyx tonight-observation journey
- Nyx observation view
- Nyx calendar/reminder journey
- Library collection contract result
- External-dependency convention
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
SkyEventTypeSchemaplus catalog identity? - Which boundary reconstructs the stored row and requires
LoggedObservationSchema.parsebefore 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?