- Opened: 2026-05-29 when collections were legacy rows with opaque item ids and no canonical Library namespace or persistence model.
- Reconciled: 2026-07-18 against the current Library contract, registry, Prisma output, migration, creation adapter, and live-DB test. This was a source reconciliation, not a fresh production walk.
- Verdict: partial — the original schema-design dependency is closed and
creation now produces a canonical collection row. The legacy table remains
read authority, unknown item ids are coerced into the
note:namespace, paired writes are non-atomic, and no edit/share/version/collaboration path proves the richer contract after creation. - Primary spec:
apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts.
Result at a glance#
| Evidence lane | Current result | Authority limit |
|---|---|---|
| Contract design | Ordered typed items, visibility, collaborators, version tag, and state ship | Encoded decisions; no public-sharing or collaboration policy validation |
| Persistence model | Registry, Prisma model, migration, canonical indexes, and tombstones ship | Repository evidence, not a production rollout receipt |
| Creation write | collections.add() inserts legacy and canonical rows |
Sequential queries; no atomicity or reconciliation mechanism |
| Item mapping | Known prefixes pass through; unknown ids become normalized note:<slug> |
Coercion can lose the source item kind and create normalized collisions |
| Automated evidence | Live-DB test verifies three mapped ids plus private/active defaults | No LibraryCollectionSchema.parse over the persisted row |
| Read/edit lifecycle | Collection listing reads the legacy table | No canonical edit, archive, share snapshot, collaborator, or read cutover |
Evidence map#
The shipped lane creates a safe private canonical projection. The richer collection behavior remains beyond that first write.
flowchart LR
A[Create collection] --> B[Legacy collection row]
B --> C[Normalize item ids]
C --> D[Canonical private active row]
D -. reads still legacy .-> E[Collection list]
D -. no connected flow .-> F[Share or version snapshot]
D -. no connected flow .-> G[Collaborator changes]
B -. no atomic rollback .-> H[Possible split write]
Proven observations#
The contract decisions now ship#
LibraryCollectionSchemapreserves item order, requires kind-prefixed item identifiers, distinguishesprivate,unlisted, andpublic, models editor and viewer collaborators, carries an optional version tag, and enforces thatitemCountmatches the list without duplicate items or collaborators.- The persistence registry includes the Library domain. The generated model maps
to
v1_library_library_collection; migration20260528022916_nyx_logged_observation_and_library_collectioncreates the canonical table and its tenant, identity, payload, live-scan, and tombstone indexes. - The old claims that the Library namespace and canonical persistence domain were missing are therefore historical, not present-tense gaps.
The initial projection is intentionally conservative#
collections.add()stores the original item ids in the legacy row. For the canonical row it preserves recognized prefixes and maps every unprefixed id to a normalizednote:id. The canonical collection beginsprivate,active, unversioned, and without collaborators or optional presentation fields.- The live-DB test creates a collection with two recognized prefixes and one raw id, then proves the canonical row contains the three expected mapped ids, the correct count, and the private/active defaults.
- Listing remains owner-scoped on the legacy table, preserving current product behavior while the canonical read model is unfinished.
Boundaries and gaps#
- A canonical write is not canonical read authority. The customer list is
still assembled from
goal3_stub_collection; canonical-only edits or repairs would be invisible. - A dual-write is not an atomic write. The two inserts are independent queries. The adapter has no transaction, outbox, or repair queue for a legacy success followed by canonical failure.
- Prefixing is not type discovery. Treating an unknown id as
note:keeps the row syntactically classifiable but does not establish what the underlying object is. Normalization can also collapse distinct raw strings to one slug. - A SQL row is not a Zod-validated collection. The generated ids and owner values can be non-UUID even though the contract requires UUIDs, and the focused test checks columns without reconstructing and parsing the object.
- Private creation is not sharing semantics. No automated path changes visibility, creates an immutable share version, prevents a private-item leak, or exercises collaborator permissions.
- Schema source is not production deployment proof. This audit did not inspect a production migration ledger or real customer collection data.
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 "collections.add\(\) dual-writes"
A green result proves the current creation projection and its defaults. It does not prove atomic writes, canonical reads, item-kind correctness, or public and collaborative collection behavior.
Source trail#
- External-dependency index
- Library contract
- Persistence registry
- Generated Prisma schema
- Canonical migration
- Postgres adapter
- Live-DB adapter test
Cross-references#
- Library save, collect, and share journey
- Library collection view
- Atelier image-to-library result
- Nyx observation contract result
- External-dependency convention
Open questions#
- What authoritative lookup classifies each legacy item id before canonical cutover, and how are normalization collisions detected?
- Which service owns collection reads and edits once the legacy table is no longer authoritative?
- Will paired writes become transactional or outbox-backed, and how will historical mismatches be inventoried and repaired?
- Which tests prove visibility inheritance, immutable shared versions, and editor/viewer permissions across mixed private and public items?
- How will existing non-UUID collection and owner identifiers be represented in
objects that must pass
LibraryCollectionSchema?