---
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: Atelier scene contract

- **Opened**: 2026-05-29 as a product-design dependency because Atelier had only
  a `goal3_stub_atelier_scene` row and no canonical scene contract.
- **Reconciled**: 2026-07-18 against the shipped contract, persistence registry,
  generated Prisma model, migration, Postgres adapter, and live-DB adapter test.
  This was a source reconciliation, not a fresh production walk.
- **Verdict**: **partial** — the original contract-design dependency is closed:
  `AtelierSceneSchema`, `v1_atelier_atelier_scene`, its migration, and a
  creation dual-write now exist. Runtime authority remains split because reads
  and later lifecycle transitions stay on legacy seams, the paired inserts are
  not one transaction, and the test does not prove the inserted row parses
  through the canonical Zod contract.
- **Primary spec**: `apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts`.

## Result at a glance

| Evidence lane       | Current result                                                                  | Authority limit                                                        |
| ------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Contract design     | Single recipient, cue privacy, render reference, share receipt, and gates ship  | Source schema; not external privacy or notification validation         |
| Persistence model   | Registry, generated Prisma model, migration, indexes, and tombstone fields ship | Presence does not establish production migration rollout               |
| Creation write      | `scenes.add()` writes legacy and canonical rows                                 | Sequential inserts; no shared transaction or repair queue              |
| Initial mapping     | Free-text recipient becomes `{ displayLabel, knownUserId: null }` in `drafting` | Render, privacy hash, share record, and notes begin null               |
| Automated evidence  | Live-DB test reads the canonical row and checks tenant, state, and recipient    | Requires configured Postgres; does not call `AtelierSceneSchema.parse` |
| Read/lifecycle path | Owner listing still reads `goal3_stub_atelier_scene`                            | No canonical finishing, sharing, acknowledgement, archive, or readback |

## Evidence map

The solid lane is implemented today. Dashed edges show where a canonical draft
row stops short of becoming the authoritative scene lifecycle.

```mermaid
flowchart LR
    A[Authenticated create scene] --> B[Legacy Atelier insert]
    B --> C[Canonical draft insert]
    C --> D[v1_atelier_atelier_scene]
    D -. reads not cut over .-> E[Owner scene list]
    D -. no connected transition .-> F[Render and cue privacy hash]
    F -. no connected transition .-> G[Share receipt and acknowledgement]
    B -. no atomic rollback .-> H[Possible split write]
```

## Proven observations

### Contract and persistence are now concrete

- `libs/contracts/src/atelier/index.ts` defines a canonical scene with an
  authenticated sender, one structured recipient, lifecycle state, cue-privacy
  manifest hash, latest render, share record, sender notes, and timestamps.
  Refinements require render evidence for `finishing` and privacy/share evidence
  for `shared`.
- `libs/oshun/persistence/src/contract-persistence-registry.ts` registers the
  contract. The generated Prisma schema maps it to `v1_atelier_atelier_scene`,
  and migration `20260528024226_atelier_studio_metis_canonical_contracts`
  creates the table and canonical identity, tenant, payload-hash, live-scan, and
  tombstone indexes.
- These facts retire the old statements that no canonical model existed or that
  product decisions still blocked defining the contract.

### Creation now leaves two records

- `scenes.add()` first inserts the legacy drafting row and then inserts a
  canonical drafting row with tenant `house`, the authenticated owner as
  `sender_user_id`, and the typed recipient projection. Unknown recipients stay
  honest as `knownUserId: null`.
- The canonical row starts with null cue-privacy, render, share, and notes
  fields. That is appropriate for a draft and avoids inventing downstream
  evidence.
- The live-DB adapter test creates a scene and reads the canonical table back,
  checking the contract schema id, initial state, recipient label, and null
  privacy hash.

## Boundaries and gaps

- **A canonical table is not canonical read authority.** `listForOwner()` still
  selects the legacy table, so a canonical-only repair or later canonical update
  would not appear in the customer list.
- **A dual-write is not an atomic write.** The stub insert and canonical insert
  are separate queries. A failure on the second query can leave a legacy row
  without its canonical peer; no outbox, reconciliation worker, or rollback is
  shown in this adapter.
- **Column assertions are not contract validation.** Generated SQL accepts text
  ids while `AtelierSceneSchema` requires UUIDs. The adapter generates a
  `scene-…` id and the focused test never parses the reconstructed row through
  the Zod schema.
- **A draft projection is not the scene lifecycle.** No connected adapter path
  populates `latestRender`, `cuePrivacyManifestHash`, `shareRecord`, recipient
  acknowledgement, or archive state.
- **A manifest hash is not privacy enforcement.** The contract encodes the
  evidence field and gates, but this result does not run intent redaction,
  compare a rendered manifest, or prove recipient-visible cues remain private.
- **Migration source is not deployment proof.** The migration exists in the
  repository; no production database inventory or rollout receipt was inspected.

## Re-run evidence

The canonical-table assertion lives in the BFF Postgres suite:

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

That test needs the repository's live Postgres test configuration. A green run
proves the initial paired-row mapping for the configured database; it must not
be summarized as canonical read cutover, atomicity, or a render-to-share flow.

## Source trail

- [External-dependency index](./README.md)
- [Atelier contract](../../../libs/contracts/src/atelier/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/20260528024226_atelier_studio_metis_canonical_contracts/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

- [Atelier image-to-library result](../atelier-image-to-library.md)
- [Scene keep-and-share journey](../../journeys/scene-keep-and-share.md)
- [Atelier new-scene view](../../customer/08-atelier/atelier-new.md)
- [Studio scene contract result](./studio-scene-contract.md)
- [External-dependency convention](./README.md)

## Open questions

- Which service owns canonical reads, and what repair process reconciles legacy
  rows created if the second insert fails?
- Will creation move to one database transaction or an outbox-backed write so
  the two records cannot silently diverge?
- Which endpoint validates and writes render, cue-privacy, share,
  acknowledgement, and archive transitions through `AtelierSceneSchema`?
- How will existing `scene-…` identifiers be migrated to a contract-valid id
  strategy without breaking legacy links?
- What production evidence confirms the migration, indexes, and cue-privacy
  gates are deployed and exercised for real tenants?
