---
status: reconciled-partial
coverage_depth: deep-for-shipped-independent-seams
last_walked: 2026-05-29
last_reconciled: 2026-07-20
specs:
  - apps/oshun/web/e2e/metis-upload-alias.spec.ts
  - apps/oshun/web/e2e/metis-byom-ingest.spec.ts
  - apps/oshun/web/e2e/metis-ingest-bff.spec.ts
  - apps/oshun/web/e2e/metis-byom-decision.spec.ts
source: WALKTHROUGH/journeys/metis-byom-ingest-to-course.md
---

# Journey result: Three stored Metis seams, not a BYOM-to-course chain

- **Walked**: 2026-05-29 at `9455bc53ad`. The retained result reported a passing
  route walk through an ephemeral `/tmp` script; it did not preserve the script,
  raw report, browser trace, request bodies, or persistence evidence.
- **Reconciled**: 2026-07-20 against the alias and proxy behavior, BYOM and
  operator fixtures, live ingest controls, deterministic outline pipeline,
  ingest and decision snapshot stores, proposal persistence tiers, account
  deletion/export composition, and all four named Playwright specs. This was
  source review, not a fresh application run.
- **Verdict**: **partial / deep for the shipped independent seams** — current
  automation deeply covers the legacy alias, deterministic text ingest, honest
  URL/PDF pending states, owner-scoped job read-back, an admin-scoped decision
  record, and a learner-owned course proposal. It does not prove that any one
  source travels through safety, rights, Sophia grounding, approval, course
  build, publication, lesson, tutor, or assessment.
- **Current authority**:
  [`WALKTHROUGH/journeys/metis-byom-ingest-to-course.md`](../journeys/metis-byom-ingest-to-course.md)
  defines the same three independent seams and partial outcome.

## Result at a glance

| Interaction                        | Proof · limit                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Legacy `/metis/upload` entry       | **Proof:** four browser cases cover a signed-in raw `308`, repeated-query preservation, canonical browser landing, 390 px standalone launch, and anonymous `307` gating to Welcome before the alias page runs. **Limit:** this proves routing into `/metis/byom`; it says nothing about source acceptance or course creation.                                                                                                                                                                           |
| BYOM page and guide preview        | **Proof:** eight of the 21 BYOM-suite cases pin the heading, four material rows, rights/status cells, drop copy, live Browse/Paste controls, five-row preview, provenance card, and action presence. **Limit:** `getMetisByom()` returns `getMetisByomFixture()` directly. The roster, generated guide, citations, lesson count, and action buttons are authored specimens, not projections of the caller's ingest jobs.                                                                                |
| Text and Markdown ingest           | **Proof:** real browser and HTTP cases post actual text; the BFF requires at least 60 trimmed characters, derives a deterministic outline, records a `completed` owner-scoped job, and returns headings, summaries, reading time, key terms, and prompts derived from that text. **Limit:** the output is an outline record, not a `MetisLearningSourceBundle`, safety result, grounded source, or course build.                                                                                        |
| URL and PDF references             | **Proof:** real HTTP and browser cases retain a locator or filename as `awaiting_source_content`, with `outline: null` and an explicit deploy-bound reason. **Limit:** the URL is not fetched and PDF bytes are not uploaded. Browse/drop turn Markdown or plain-text bytes into `kind: text`; EPUB and DOCX are not live ingest types.                                                                                                                                                                 |
| Ingest persistence and ownership   | **Proof:** the route awaits an ordered snapshot commit before returning `202`; publication follows persistence, production requires the durable sink, owner-filtered reads return `403` on mismatch, and unit plus PostgreSQL integration evidence covers restart, failed-write rollback, export, erasure, and a hashed recreation fence. **Limit:** the four named application suites do not restart the deployed backing tier, and the snapshot abstraction does not join the job to a source bundle. |
| Operator decision                  | **Proof:** seven cases cover approve, request changes, reject, state mapping, newest-first filtered read-back, a 12-character summary gate, `401`/`403`, UI reflection, and non-null audit ids in the test environment. The review row commits before `201`; production requires its durable sink, and operator deletion pseudonymizes it for retention. **Limit:** the page source id is synthetic fixture text, audit mirroring is best-effort, and no approval gate or bundle mutation follows.      |
| Learner course proposal            | **Proof:** real browser and HTTP cases create an owner-scoped `awaiting-syllabus-review` draft and read it back on `/metis`; a short title returns `422`. PostgreSQL mode also dual-writes an empty canonical `awaiting-triage` proposal. **Limit:** the proposal request has no ingest-job or decision id, named automation keeps one process, fallback file persistence is best-effort, and no reviewer transition or build-spawn route follows.                                                      |
| Published course and learner reads | **Proof:** none in these four specs. **Limit:** no runtime join composes the three records, invokes safety/rights/PII or Sophia/knowledge-graph work, applies `ByomSourceApprovalGate`, promotes the canonical proposal, creates a `CourseBuild`, or makes the ingested source available to lesson, tutor, assessment, or library consumers.                                                                                                                                                            |

## Evidence map

The vertical map preserves reading order at narrow widths. Solid edges are
exercised by the named suites. Dashed edges are the joins the current runtime
does not make; the stacked operator and proposal records are separate inputs,
not later stages of the ingest job.

```mermaid
flowchart TB
    A[BYOM fixture plus live ingest] --> B[Owner-scoped job and optional outline]
    B -. no bundle creation .-> C[Stops before safety and grounding]
    C -. no build trigger .-> D[No published learner read]
    D ~~~ E[Operator fixture queue]
    E --> F[Decision for synthetic source id]
    F -. no job resolution or gate .-> G[Stored verdict only]
    G ~~~ H[Independent learner proposal]
    H --> I[Awaiting-review draft]
    I -. no review or promotion worker .-> J[Stored proposal only]
```

## Proven observations

### The historical pass is a dated route observation

- The old result proves that `/metis/ingest` and `/metis/courses/new` rendered
  at the walked commit, that both endpoints accepted a happy-path request, and
  that `/metis/upload` redirected. Its zero-error and zero-failure statements
  cannot be reproduced from the repository because the `/tmp` walker and its
  artifacts were not retained.
- Current evidence is stronger but changes the grade. The four named files
  contain **37 cases**: four alias, 21 BYOM/operator/proposal browser, five real
  ingest/proposal HTTP, and seven decision HTTP/UI cases. Their breadth proves
  multiple shipped seams; it also exposes that those seams do not call one
  another.
- The current result therefore does not reuse the old generic `pass`. A route
  existing is weaker than the journey title's former ingest-to-course promise.

### The canonical entry lands on a fixture with a live ingest island

- `/metis/upload` uses Next's `permanentRedirect`, producing the tested raw
  `308` for an authenticated request. It preserves scalar and repeated query
  parameters. The application proxy gates an anonymous request to `/welcome`
  first, so the redirect component never renders for that request.
- `/metis/byom` calls `getMetisByom()`, which returns `getMetisByomFixture()`
  without a BFF read. Four books/notes, page counts, rights chips, parse states,
  the five-section Stoic outline, “11 lessons · 6 weeks,” and the provenance
  paragraph are authored data.
- The page is not wholly static. `MetisByomIngestControls` is a client island
  that posts to the real ingest route. Its Browse and drop paths read Markdown
  or plain-text bytes locally and send them as text; PDF sends only the file
  name; Paste URL sends only the locator.
- The “Generate guide,” “Edit outline,” and “Begin week 1” cases deliberately
  assert presence without clicking. Those controls have no handler or target in
  `MetisBYOM`. Likewise, fixture copy saying every claim is cited and lessons
  were reviewed is not evidence that the current caller's source received either
  treatment.
- The client enforces one 50 MB per-file check. The BFF does not repeat that
  byte limit, the controls take only the first dropped/selected file, and the
  displayed 200 MB aggregate limit has no live quota implementation.

### Text ingest produces a real, deterministic outline record

- `POST /v1/metis/ingest` is origin-, CSRF-, and bearer-authentication guarded.
  Its shared auth pre-handler does not require a Metis-specific scope. Supported
  kinds are exactly `text`, `url`, and `pdf`; text shorter than 60 trimmed
  characters, a non-HTTP(S) URL, an empty PDF filename, and an unknown kind are
  rejected before a job is created.
- `deriveStudyOutline` operates only on supplied text. It merges short
  paragraphs, caps output at eight segments, derives a heading and bounded
  summary per segment, estimates reading time at 200 words per minute, extracts
  repeated non-stopword terms, and constructs study prompts. It is heuristic and
  deterministic, not a model call.
- The route returns `202` with `state: completed` and the derived outline. The
  owner can read the stored job through `GET /v1/metis/ingest/:jobId`; an
  unknown id returns `404`, while an existing job requested by another owner
  returns `403 job_owner_mismatch`.
- The activity route can project a completed ingest job into the member's
  activity feed. That read-back still points generally to Metis; it does not
  make the outline a lesson or establish a source-bundle lifecycle.

### URL and PDF outcomes are honest references, not extraction

- A URL job stores the URL string and says URL fetch plus extraction is
  deploy-bound. A PDF job stores the filename and says parsing is deploy-bound.
  Both return `awaiting_source_content`, `outline: null`, and no invented
  segments.
- The dedicated `/metis/ingest` form's PDF picker also retains only a filename.
  The BYOM island accepts `.pdf`, `.md`, and `.txt`; it does not accept the EPUB
  and DOCX kinds shown in the fixture roster.
- This is a sound non-fabrication boundary, not a queue. There is no worker,
  callback, source-byte attachment route, or status transition that moves an
  awaiting reference to `completed` later.

### Ingest acknowledgment is now durability-bound and privacy-composed

- `wireDurableMetisIngestJobs` hydrates the `metis-ingest-jobs` snapshot before
  traffic, while `requireDurableMetisIngestJobs` makes an unwired deployable
  fail closed. The snapshot includes minimized jobs and SHA-256 deletion fences.
- The deployable route uses `createDurably()`: it clones the candidate map,
  validates per-subject and total capacity, awaits the snapshot sink, and only
  then publishes the job and returns `202`. Persistence failure returns `503`
  without exposing the candidate; a previously erased subject receives `410`.
- Raw pasted text is not retained. Completed text jobs keep only the derived
  outline, while URL locators are minimized and PDFs keep a normalized filename.
  This is durable derived state, not a retained upload body.
- The `saved_claim_passage` export includes the caller's `metis_ingest_jobs`.
  Deletion removes the exact owner's jobs, writes the fence in the same ordered
  snapshot, and blocks resurrection. Unit and live-PostgreSQL integration tests
  cover these storage properties; the four named browser/HTTP suites still do
  not recycle a deployed process.

### The operator panel stores a verdict against synthetic fixture text

- `/operator/metis` gets its KPI strip, five generated-lesson rows, quality
  scores, rights/source counts, Themis flags, and tutor telemetry from
  `getMetisOperatorFixture()`. Only the OneRoster card can be adapted from a
  live BFF read.
- Each `MetisByomDecisionPanel` receives a source id assembled as
  `${course}::${lesson}` from a fixture row. The decision route checks admin
  scope and validates a non-empty source id, one of three decision verbs, and a
  trimmed summary of at least 12 characters. It never resolves that string to an
  ingest job or learning-source bundle.
- Approve maps to `approved`, request changes to `changes-requested`, and reject
  to `rejected`. Each submission creates another row; list reads are
  newest-first and may filter by exact source id. “Amend decision” merely
  returns the local panel to its input state; the next submit appends a new row
  rather than changing the old one.
- The authoritative review row commits first through `recordDurably()`. The
  route then attempts to append and flush a searchable admin-audit mirror and
  durably link its id; mirror failure is swallowed, leaving a complete review
  row with `auditEventId: null`. The HTTP cases prove non-null ids only in their
  configured test environment.
- Production requires the decision snapshot sink. Mutations are serialized,
  persisted before publication, and mapped to `503` on persistence failure.
  Account deletion pseudonymizes matching operator ids and free-text rationales,
  fences recreation, and retains the internal review package until the canonical
  compliance window permits purge. It is intentionally not customer export data.
  The named seven-case spec does not restart the BFF.
- The three UI cases intercept the panel request and reissue it to the real BFF
  with a test-authored `admin:metis` bearer. They prove the genuine route and UI
  state mapping, but not that an ordinary browser session carries a real
  operator credential end to end.
- The panel labels an approved row “cleared for course build” and changes
  requested as “returned to editor.” Those are display phrases only: no build or
  editor record is changed by the route.

### The learner proposal persists on a different plane

- `POST /v1/metis/courses` requires a bearer-resolved member, origin, and CSRF
  context, but no Metis-specific scope; rejects titles shorter than four
  characters; normalizes domain text to uppercase; and accepts an in-range 1–26
  week count or falls back to six. It creates an owner-scoped legacy proposal in
  `awaiting-syllabus-review` with a fixed three-day review estimate.
- `/v1/metis/courses` and `/v1/metis/room` list only the caller's proposals. The
  browser case submits a draft, visits `/metis` in the same process, and finds
  that exact title at the head of the learner's room.
- The proposal store chooses PostgreSQL when available, a per-user development
  JSON file outside tests, and memory in tests without a persistence path. File
  writes are best-effort and corrupt/unreadable files start empty. The named
  browser and HTTP cases do not prove a restart at any of those tiers.
- In PostgreSQL mode, creation sequentially inserts the legacy proposal and a
  canonical `v1_metis_course_proposal` peer in `awaiting-triage`. The canonical
  row starts with empty seed sources, review chain, and spawned-build ids. The
  two inserts are not wrapped in a transaction, and current list reads still use
  the legacy row.
- No named request contains an ingest job, decision, source bundle, or canonical
  proposal id as an upstream reference. The result card's promise that Metis
  returns within three days with an outline is not backed by a scheduler or
  reviewer route.
- The current account-deletion composition registers no course-proposal eraser
  and the default export bundle does not collect proposals. Operator decisions
  follow a different internal-governance lifecycle: exact operator deletion
  pseudonymizes identity and rationale, retains a digest and purge schedule, and
  excludes the review package from the customer bundle.

## Boundaries and gaps

- **A query-preserving alias is not ingest.** It proves navigation to the
  canonical page, not source validation or a stored job.
- **A fixture material row is not an ingest job.** The four materials and five
  outline rows render for every caller and are not read from
  `metisIngestJobStore`.
- **Fixture provenance copy is not provenance evidence.** No runtime citation,
  verbatim-quotation, or source-only review record backs that preview.
- **A 50 MB client check is not a server quota.** The BFF does not enforce the
  byte limit, and the displayed 200 MB aggregate has no live counter.
- **A PDF filename is not an uploaded PDF.** No file bytes reach the route, so
  parsing cannot begin.
- **A URL locator is not fetched content.** Awaiting state has no worker or
  later content-attachment transition.
- **A deterministic outline is not a grounded learning source.** It is a
  heuristic projection over caller-supplied text with no safety, rights, PII,
  Sophia, embedding, or knowledge-graph receipt.
- **Authentication is not Metis authorization.** The ingest and proposal seams
  resolve a valid bearer but do not enforce a Metis-specific domain scope.
- **Write-before-ack durability is not lifecycle linkage.** Ingest jobs and
  review rows now commit before success, but neither record becomes a source
  bundle, approval-gate input, or course-build trigger.
- **A same-process reload is not a restart proof.** The named application specs
  never recycle the configured BFF/database tier.
- **One composed privacy partition is not universal coverage.** Ingest jobs are
  exported, erased, and fenced; operator reviews are pseudonymized and retained.
  Course proposals still have no registered export or account-deletion path.
- **A synthetic source id is not source existence.** The decision route accepts
  any non-empty string and does not resolve it to a job or bundle.
- **A mapped `approved` state is not approval-gate execution.** Shared unit
  tests cover `applyByomSourceApprovalDecision`; the BFF/UI never calls it.
- **An audit attempt is not an audit guarantee.** Failure is explicitly
  swallowed and the decision may persist with `auditEventId: null`.
- **A test-authored admin bearer is not production operator authentication.**
  The browser decision cases bridge customer page traffic to the real BFF with
  their own admin token.
- **“Cleared for course build” is not a build trigger.** The decision panel
  changes its local display after a stored row; no `CourseBuild` is created.
- **An awaiting-triage row is not a review workflow.** No actor-bound triage,
  grounding, Veritas, rejection, or promotion transition is wired here.
- **A dual-write is not atomic or canonical read authority.** Legacy and
  canonical proposal inserts are sequential, and customer reads remain on the
  legacy table.
- **A learner draft is not a course.** It has no source ids, approved bundle,
  lessons, tutor state, assessment, or publication receipt.
- **Action presence is not interaction coverage.** Generate guide, Edit outline,
  and Begin week 1 are intentionally presence-only assertions.
- **Deep seam coverage is not a complete journey.** Thirty-seven cases can be
  green while the safety-to-publication spine remains entirely absent.
- **Source reconciliation is not execution.** None of the four cited application
  specs was rerun for this documentation audit.

## Re-run evidence

Run the four focused specs sequentially against one immutable commit and one
controlled web/BFF environment:

```bash
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/metis-upload-alias.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/metis-byom-ingest.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/metis-ingest-bff.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/metis-byom-decision.spec.ts --project=chromium --workers=1
```

Retain the commit and environment mode, web/BFF reports, source request bytes or
references, authenticated learner/operator subjects, generated job and decision
ids, audit ids, storage tier, pre/post snapshots, and process-restart read-back.
A journey-complete follow-up must also retain the validated source bundle id,
ordered safety/rights/PII receipts, Sophia and knowledge-graph anchors,
approval-gate input/output, linked canonical proposal review entries, atomic
promotion/build receipt, published course id, and lesson/tutor/assessment reads
of that exact source.

## Source trail

- [Current journey contract](../journeys/metis-byom-ingest-to-course.md)
- [Legacy alias route](../../apps/oshun/web/src/app/metis/upload/page.tsx)
- [BYOM fixture source](../../apps/oshun/web/src/lib/lilith-data/metis-depth.ts)
- [BYOM page and operator surface](../../apps/oshun/web/src/components/lilith/metis.tsx)
- [Live BYOM ingest controls](../../apps/oshun/web/src/components/lilith/MetisByomIngestControls.tsx)
- [Dedicated ingest form](../../apps/oshun/web/src/app/metis/ingest/MetisIngestForm.tsx)
- [Deterministic outline pipeline](../../apps/oshun/bff/src/metis/ingest-pipeline.ts)
- [Ingest job store](../../apps/oshun/bff/src/metis/ingest-job-store.ts)
- [Ingest and proposal routes](../../apps/oshun/bff/src/routes/domain-stubs.ts)
- [Proposal persistence adapter](../../apps/oshun/bff/src/routes/domain-stubs-postgres.ts)
- [Decision panel](../../apps/oshun/web/src/components/lilith/MetisByomDecisionPanel.tsx)
- [Decision route](../../apps/oshun/bff/src/routes/admin-metis-byom-decision.ts)
- [Decision store](../../apps/oshun/bff/src/metis/metis-byom-decision-store.ts)
- [Uninvoked BYOM approval contract](../../libs/metis/integrations/src/byom/index.ts)
- [Alias browser evidence](../../apps/oshun/web/e2e/metis-upload-alias.spec.ts)
- [BYOM/operator/proposal browser evidence](../../apps/oshun/web/e2e/metis-byom-ingest.spec.ts)
- [Ingest/proposal HTTP evidence](../../apps/oshun/web/e2e/metis-ingest-bff.spec.ts)
- [Decision HTTP/UI evidence](../../apps/oshun/web/e2e/metis-byom-decision.spec.ts)

## Cross-references

- [Metis proposal contract result](./external-dependencies/metis-course-proposal-contract.md)
  — canonical initial write, review-chain, atomicity, and promotion boundaries.
- [Metis lesson completion result](./metis-lesson-completion.md) — existing
  lesson, assessment, appeal, and tutor surfaces; they do not consume this
  journey's new ingest job.
- [Editorial review and approval result](./editorial-review-approval.md) — a
  separate approval plane, not the missing Metis BYOM gate.
- [Operator Metis view](../operator/operator-metis.md) and
  [customer BYOM view](../customer/08-metis/metis-byom.md) — the two fixture-led
  surfaces.
- [Results convention](./README.md) — status vocabulary and evidence rules.

## Open questions

- Which service turns an ingest job into a typed, tenant-scoped
  `MetisLearningSourceBundle`, and how are source bytes retained or referenced?
- What ordered operation records safety, rights, PII, Sophia ingestion,
  embeddings, knowledge-graph anchors, and retraction-aware evidence?
- How will an operator decision resolve an existing bundle, invoke
  `ByomSourceApprovalGate`, reject stale/conflicting state, and retain an
  audit-guaranteed receipt?
- What transaction or outbox links an approved bundle and reviewed proposal to a
  `CourseBuild` without split legacy/canonical writes?
- Which read proves the exact published source reaches a lesson, tutor turn,
  assessment, citation, and learner library record?
- What cross-instance concurrency contract prevents competing whole-snapshot
  ingest or decision writers from overwriting one another?
- How will course proposals participate in customer export, account deletion,
  erasure fencing, and retention alongside the already-composed ingest and
  internal-review partitions?
- Which BYOM preview claims and action labels should remain hidden until their
  cited, reviewed, and publishable states are backed by runtime evidence?
