- Opened: 2026-05-29 as a product-design dependency because Atelier had only
a
goal3_stub_atelier_scenerow 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.
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.tsdefines 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 forfinishingand privacy/share evidence forshared.libs/oshun/persistence/src/contract-persistence-registry.tsregisters the contract. The generated Prisma schema maps it tov1_atelier_atelier_scene, and migration20260528024226_atelier_studio_metis_canonical_contractscreates 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 tenanthouse, the authenticated owner assender_user_id, and the typed recipient projection. Unknown recipients stay honest asknownUserId: 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
AtelierSceneSchemarequires UUIDs. The adapter generates ascene-…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
- Atelier contract
- Persistence registry
- Generated Prisma schema
- Canonical migration
- Postgres adapter
- Live-DB adapter test
Cross-references#
- Atelier image-to-library result
- Scene keep-and-share journey
- Atelier new-scene view
- Studio scene contract result
- External-dependency convention
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?