---
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: Metis course-proposal contract

- **Opened**: 2026-05-29 to define the upstream creator proposal separately from
  the curated `MetisCourseBuild` it may eventually produce.
- **Reconciled**: 2026-07-18 against the shipped proposal contract, persistence
  registry, generated Prisma model, migration, creation adapter, and live-DB
  test. This was a source reconciliation, not a fresh production walk.
- **Verdict**: **partial** — proposer identity, review chain, seed sources,
  promotion links, and rejection semantics now have a canonical schema and
  initial dual-write. The runtime only creates an empty `awaiting-triage`
  projection; review, grounding, Veritas, promotion, rejection, canonical reads,
  atomicity, and contract-valid ids remain unproved.
- **Primary spec**: `apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts`.

## Result at a glance

| Evidence lane       | Current result                                                                  | Authority limit                                                      |
| ------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Contract design     | Proposer, sources, four-step review, promotion links, and rejection states ship | Encoded policy; no reviewer or governance validation                 |
| Persistence model   | Registry, Prisma table, migration, canonical indexes, and tombstones ship       | Repository evidence, not a production rollout receipt                |
| Creation write      | `courseProposals.add()` inserts legacy and canonical rows                       | Sequential queries; no transaction or repair mechanism               |
| Initial mapping     | Legacy syllabus-review state maps to `awaiting-triage`; arrays start empty      | Does not perform triage, grounding, Veritas review, or promotion     |
| Automated evidence  | Live-DB test checks tenant, proposer, weeks, state, and empty arrays            | No Zod parse and no lifecycle transition assertions                  |
| Read/lifecycle path | Proposal listing still reads the legacy table                                   | No canonical review receipt, rejection, build spawn, or read cutover |

## Evidence map

The current implementation creates the durable starting point. The governed path
from idea to course remains dashed.

```mermaid
flowchart LR
    A[Submit course idea] --> B[Legacy proposal row]
    B --> C[Canonical awaiting-triage row]
    C -. not connected .-> D[Metis triage]
    D -. not connected .-> E[Sophia grounding]
    E -. not connected .-> F[Veritas review]
    F -. not connected .-> G[Spawn course build]
    C -. reads still legacy .-> H[My proposals]
    B -. no atomic rollback .-> I[Possible split write]
```

## Proven observations

### Proposal and build are now distinct contracts

- `MetisCourseProposalSchema` carries the proposer, tenant, title, domain path,
  target weeks, rationale, up to three source references, a bounded review
  chain, lifecycle state, spawned build ids, rejection notes, and timestamps.
- The review vocabulary explicitly distinguishes triage, grounding, Veritas, and
  ready steps. Soft rejection leaves a proposal open for another pitch; hard
  rejection is the terminal/tombstone case; promotion requires at least one
  spawned build id.
- The registry and generated Prisma model map the contract to
  `v1_metis_course_proposal`. Migration
  `20260528024226_atelier_studio_metis_canonical_contracts` creates the table
  and standard canonical indexes.

### Creation records an honest empty review state

- `courseProposals.add()` writes the legacy `awaiting-syllabus-review` row, then
  writes its canonical peer as `awaiting-triage`, using the authenticated owner
  as proposer. Source refs, review entries, and spawned builds start empty;
  rejection notes start null.
- The live-DB test reads the canonical peer and verifies the tenant, contract
  schema, proposer, target weeks, state mapping, and empty source/build arrays.
- The list path remains backed by `goal3_stub_course_proposal`, so the new row
  is a write-side bridge rather than the authoritative proposal service.

## Boundaries and gaps

- **An awaiting-triage row is not a review workflow.** No endpoint appends an
  actor-bound triage, grounding, or Veritas verdict or enforces step ordering.
- **A promotion field is not promotion.** No connected route creates a
  `MetisCourseBuild`, writes `spawnedBuildIds`, and returns a linked receipt.
- **A canonical table row is not necessarily contract-valid.** The adapter
  generates `course-proposed-…` ids and accepts owner strings, although the
  contract requires UUIDs. The test checks columns without parsing the object.
- **A dual-write is not atomic.** The legacy and canonical inserts are separate
  statements. No transaction, outbox, or repair worker closes partial failure.
- **A canonical write is not canonical read authority.** Lists stay on the
  legacy table, so canonical review state would not automatically appear in the
  current customer surface.
- **Review vocabulary is not governance proof.** This result does not validate
  reviewer permissions, source grounding quality, retraction conflicts,
  notifications, or appeal/re-pitch semantics.

## 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 "courseProposals.add\(\) dual-writes"
```

A green result proves the current initial projection. It must not be reported as
a review chain, a grounded proposal, a promoted course build, or canonical read
cutover.

## Source trail

- [External-dependency index](./README.md)
- [Metis proposal contract](../../../libs/contracts/src/metis/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

- [Metis proposal view](../../customer/06-metis/metis-propose.md)
- [Metis BYOM journey](../../journeys/metis-byom-ingest-to-course.md)
- [Editorial review and approval result](../editorial-review-approval.md)
- [Studio scene contract result](./studio-scene-contract.md)
- [External-dependency convention](./README.md)

## Open questions

- Which service records each review entry, enforces reviewer role and step
  order, and exposes the receipt to the proposer?
- How are seed sources grounded and checked against Veritas conflicts before a
  proposal can become `ready-for-promotion`?
- What atomic operation spawns one or more builds and updates the proposal
  without orphaning either side?
- How will current non-UUID proposal identifiers be reconciled with the
  canonical contract?
- When will reads move to the canonical model, and what process repairs split or
  historically missing peers before cutover?
