---
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: Library collection contract

- **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.

```mermaid
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

- `LibraryCollectionSchema` preserves item order, requires kind-prefixed item
  identifiers, distinguishes `private`, `unlisted`, and `public`, models editor
  and viewer collaborators, carries an optional version tag, and enforces that
  `itemCount` matches the list without duplicate items or collaborators.
- The persistence registry includes the Library domain. The generated model maps
  to `v1_library_library_collection`; migration
  `20260528022916_nyx_logged_observation_and_library_collection` creates 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 normalized `note:` id. The canonical collection begins `private`, `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](./README.md)
- [Library contract](../../../libs/contracts/src/library/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

- [Library save, collect, and share journey](../../journeys/library-save-collection-share.md)
- [Library collection view](../../customer/02-home-discovery/library-collections.md)
- [Atelier image-to-library result](../atelier-image-to-library.md)
- [Nyx observation contract result](./nyx-logged-observation-contract.md)
- [External-dependency convention](./README.md)

## 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`?
