# Journey: Arete Living Offering create

A user states an intention on `/arete/offering`, previews the fixture-backed 4–8
minute composition, asks the framework-aware coach for guidance, and then keeps
or sends the offering after Lilith's safety pre-screen. The walk does not claim
that the coach response dynamically rewrites the score grid: those are separate
shipped interactions. It verifies that the offering, safety, coach, receipt, and
member-scoped read-back paths do not expose the private intention outside the
originating user's records.

## Personas

- **First-offering user** — has Arete cadence but has never composed an
  offering; lands on `/arete/offering` empty (the fixture currently still
  renders a populated draft — see Failure modes)
- **Returning composer** — `/arete/offering` resumes a draft
- **Coach-mode picker** — toggles framework between
  `general / cbt / act / stoic / reflective` before letting the score render
- **At-risk user** — input prose carries a distress signal; Lilith intercepts
  via the [Crisis-aware tone policy](./crisis-aware-tone-policy.md) before the
  score composes
- **Tenant-bound user** — declared residency must scope both the coach BFF and
  the kept artifact

## Pre-conditions

- Signed-in session; middleware permits `/arete/offering`, `/arete/coaching`,
  `/profile/safety`
- `getAreteOffering()` returns `AreteOfferingData` ({ sceneTimeline,
  scoreOptions, toneSwatch, sharingRules }); BFF mapping comment marks
  `/arete/offerings/:id` (see
  [`customer/04-arete/arete-offering.md`](../customer/04-arete/arete-offering.md))
- Lilith policy substrate wired into the keep/send action surface per
  [`V1/features.md#crisis-aware-behavior`](../../V1/features.md#crisis-aware-behavior)
- Coach BFF `POST /v1/arete/coach/responses` reachable (route in
  `apps/oshun/bff/src/routes/arete.ts`); direct chat route in
  `apps/oshun/web/src/components/domains/arete/AreteAICoach.tsx` and local
  fallback composer in
  `apps/oshun/web/src/components/domains/arete/AreteCoach.tsx`
- No Lilith crisis frame currently active

## Steps

### 1. Land on the offering surface

- [ ] Navigate to `/arete/offering`; `page.tsx` awaits `getAreteOffering()` →
      `<AreteOffering>` inside an `LWebShell` with `LCustomerNav active="today"`
- [ ] Masthead reads "Arete · the living offering" / "An intention, _composed_
      into a small scene." with the 4–8 minute lede
- [ ] **Verify**:
      [`customer/04-arete/arete-offering.md`](../customer/04-arete/arete-offering.md)
      layout regions

### 2. Capture the intention

- [x] User enters intention prose in the "I · The intention" card; the shipped
      surface now renders a real textarea with
      `[data-arete-offering-intention-input]`, and the scene preview line
      follows edited prose
- [x] User edits the recipient and occasion in Section V; the sharing controls
      update the masthead summary, send label, and BFF-backed action island
      state before keep/send

### 3. Lilith crisis pre-screen

Same hook walked in
[`crisis-aware-tone-policy.md`](./crisis-aware-tone-policy.md) §1–§3 applied to
Arete intent capture.

- [x] On keep/send submit, `AreteOfferingActions` calls
      `/v1/lilith/adapter/safety-assessment?role=safety` with the browser
      intention and `region: 'US'` before writing `/v1/arete/offerings/keep`
- [x] **If signal ≥ threshold**: the keep/send write aborts, no offering is
      persisted, and `[data-arete-offering-safety-interrupt]` mounts in the
      action region with crisis resources and a `/profile/safety` link. The full
      durable crisis-frame activation/audit cascade remains covered by
      [`crisis-aware-tone-policy.md`](./crisis-aware-tone-policy.md), not by
      this client-side preflight.
- [ ] **If adjacent (below threshold)**: substrate applies the `reflective` tone
      band — coach scoring leans reflective even if user picked another
- [x] **Clean path**: substrate marks intention `safe`; flow continues to the
      draft/send keep route

### 4. Coach scoring (framework selector)

- [x] User picks framework (CBT / ACT / Stoic / Reflective / general) directly
      on `/arete/offering` through
      `[data-arete-offering-coach-framework-option]`; the panel exposes the
      selected BFF framework on `[data-arete-offering-coach-panel]`
- [x] Client POSTs `{ framework, userMessage }` to `/v1/arete/coach/responses`;
      BFF calls `composeAreteCoachResponse(input)` from
      `apps/oshun/bff/src/arete/coach-responses.ts` — deterministic
      keyword-routed templates (CBT separates situation/thought/feeling; Stoic
      invokes dichotomy of control; ACT pivots on values; Reflective asks for a
      one-paragraph honest pause)
- [x] On non-2xx: `/arete/offering` renders `[data-arete-offering-coach-error]`,
      clears stale preview copy, leaves the preview button enabled, and a second
      click can retry into the live BFF
- [ ] Cross-ref:
      [`V1/features.md` § Living Offerings](../../V1/features.md#living-offerings);
      coverage.md finding #9 documents this pipeline

### 5. Score renders into the composition

- [x] User can change the selected III · Score option in the 3-column
      `data.scoreOptions` grid; the initial selection is seeded from the
      selected fixture option, and the active option exposes `aria-pressed`
- [ ] Coach response still does not dynamically compose the `/arete/offering`
      score grid; the route renders fixture-backed score options
- [x] II · Scene preview (16/9 simulated video, "▶" glyph) renders with
      `[data-arete-offering-scene-label]` derived from `sceneTimeline[0]` and
      `[data-arete-offering-scene-preview-line]` derived from the current
      intention
- [x] Scene timeline strip (waveform SVG, scrub head at 12%), IV · Materials 3x3
      swatch grid driven by `data.toneSwatch` with tile select + drag/drop +
      Move earlier / Move later reorder controls, V · Sharing recipient/occasion
      controls + `data.sharingRules` rows all render
- [x] Cue-privacy promise card (accent-bordered) carries the "working copy is
      destroyed" copy, "Read the full promise" opens
      `[data-arete-offering-promise-detail]`, and "See the C2PA receipt" calls
      `/v1/arete/offerings/receipt` to render a member-scoped receipt envelope
      with C2PA/cue-privacy/private-intent hashes

### 6. Keep — the private artifact

- [x] Click "Save draft" (`AreteOfferingActions` button); client pre-screens the
      browser intention through Lilith, then POSTs `/v1/arete/offerings/keep`
      with `{ recipient, occasion, action: 'draft', intention }` and renders
      `data-arete-offering-state="draft-kept"`
- [x] Expected: write to Arete offerings store with draft state per
      [`V1/features.md#living-offerings`](../../V1/features.md#living-offerings)
      ("kept in their gallery as a personal artifact, and re-renderable forever
      from the underlying score")
- [x] Cue-privacy promise: intent layer scoped to `userId`; BFF must not surface
      intent prose in any aggregate
- [ ] Telemetry (when wired): `arete_offering_kept`

### 7. Optional — share

- [x] Click "Send to {recipient} · {occasion}" (`AreteOfferingActions` button);
      client pre-screens the browser intention through Lilith, then POSTs
      `/v1/arete/offerings/keep` with
      `{ recipient, occasion, action: 'send', intention }` and renders
      `data-arete-offering-state="delivered"`
- [x] Expected: share decision persists to the Arete offerings store and can be
      read back from `/v1/arete/offerings/sent`; policy still needs to be
      reconciled with the Living Scenes shareability matrix per
      [`V1/features.md#living-scenes`](../../V1/features.md#living-scenes);
      "watch once · on a single device" enforcement per the sharing rules
- [ ] Telemetry (when wired): `arete_offering_shared`

## Post-conditions

- Intention crisis-pre-screened at keep/send time; either a blocking result
  terminates in the action-region safety interruption, or the substrate marks
  the intention `safe` and the write proceeds
- A score composed for the picked framework — different frameworks produce
  measurably different prose; BFF outage renders an honest recoverable preview
  error instead of stale or simulated score copy
- If Save: offering record is accepted by the Arete offerings store and the UI
  renders `draft-kept`; the signed-in `/arete/offerings` gallery reads the
  member-scoped live row back with the private intention preview
- If Send: share record persists and is returned by `/v1/arete/offerings/sent`;
  the signed-in `/arete/offerings` gallery also reads the sent row back from the
  live store with the selected recipient and occasion; watch-policy / expiry
  enforcement remains an open gap against the Living Scenes shareability matrix
- Private intent layer scoped to originating user — the keep/send preview is
  member-scoped, and the C2PA receipt envelope hashes the raw intention instead
  of rendering it back in the browser

## Failure modes

- [x] **Save draft / Send are wired** — `AreteOfferingActions` replaced the
      inert footer buttons; both actions POST `/v1/arete/offerings/keep` and
      render BFF-backed status (`draft-kept` / `delivered`)
- [x] **Save draft / Send write returns 5xx** — the action island renders
      `[data-arete-offering-error][role="alert"]`, keeps the active control
      enabled, and a second browser click retries the same intention through the
      real BFF to `draft-kept` or `delivered`
- [x] **Read promise / See C2PA receipt are wired** — the promise button toggles
      `[data-arete-offering-promise-detail]`; the receipt button POSTs the
      current recipient, occasion, intention, and selected score to
      `/v1/arete/offerings/receipt`, renders `receipt-issued`, C2PA manifest id,
      cue-privacy hash, and private-intent hash, and does not render raw
      intention prose in the receipt panel
- [x] **Recipient and occasion selection** — Section V recipient/occasion inputs
      update the send target, the browser POST body, `/v1/arete/offerings/sent`
      readback, and the `/arete/offerings` gallery row
- [x] **Dynamic masthead / scene summary** — the masthead summary follows edited
      recipient + occasion, the scene label is derived from `sceneTimeline[0]`,
      and the scene preview line follows edited intention
- [x] **Score selection state** — score is no longer hardcoded by index; the
      client control seeds from the selected `scoreOptions` field and updates on
      click
- [x] **Crisis pre-screen bypassed if substrate not wired** — keep/send now
      calls the Lilith safety assessment route before writing; a blocking
      crisis-like intention mounts `[data-arete-offering-safety-interrupt]` and
      makes no `/v1/arete/offerings/keep` call
- [x] **Safety preflight unavailable** — a non-2xx
      `/v1/lilith/adapter/safety-assessment` response renders
      `[data-arete-offering-error][role="alert"]`, leaves Save draft / Send
      enabled for retry, and still makes no `/v1/arete/offerings/keep` call
- [ ] **Pre-submit composition can still preview crisis prose** — the live scene
      preview and fixture score grid update before the keep/send safety
      preflight; `/arete/offering` still needs a product decision on whether
      score preview should also be halted before action submit
- [x] **Coach BFF returns 5xx on `/arete/offering`** — the coach-preview panel
      renders `[data-arete-offering-coach-error]`, clears any stale response,
      leaves the preview button enabled, and retries the same intention +
      framework into the live BFF
- [x] **Direct coach page BFF returns 5xx** — `/domains/arete/coach` renders an
      honest "coach could not be reached" bubble and no fake suggestion chips
- [ ] **Framework mismatch** — `CoachMode` includes
      `goals / habits / motivation` which all fall through to `general` per
      `modeToBffFramework`; user picks "Goals" expecting goal-specific scoring
      and gets the general lens
- [x] **Material reorder controls** — the caption now matches real tile
      selection plus drag/drop and Move earlier / Move later controls
- [ ] **Arete binary C2PA signing remains render-pipeline scoped** — the
      offering surface now has a real BFF receipt envelope, but the route still
      does not invoke the Living Scenes/Aja media renderer to sign concrete
      output bytes

## E2E coverage

- [`apps/oshun/web/e2e/arete-living-offering-create.spec.ts`](../../apps/oshun/web/e2e/arete-living-offering-create.spec.ts)
  — steps 1, 2 & 5: the `/arete/offering` composition surface — masthead +
  Lilith identity, the editable intention textarea (I) feeding the scene preview
  line, scene preview (II) + timeline with a scene label derived from
  `sceneTimeline[0]`, score grid (III) including click-to-select state,
  materials swatch (IV) including selected-tile reorder via Move earlier / Move
  later, sharing controls (V) including recipient/occasion edits that update the
  masthead summary and send target, and the cue-privacy promise including the
  full-promise reveal.
- [`apps/oshun/web/e2e/arete-offering-actions.spec.ts`](../../apps/oshun/web/e2e/arete-offering-actions.spec.ts)
  _(wave7)_ — **steps 6–7 (keep / send) at the real BFF layer**: "Save draft"
  pre-screens the browser-entered intention through the real
  `/v1/lilith/adapter/safety-assessment?role=safety` BFF route, POSTs clean
  intentions to `/v1/arete/offerings/keep`, renders **draft-kept** with the
  private intention preview, and "Send" POSTs the same route to render
  **delivered**. The crisis branch enters a blocking intention, observes the
  real browser `POST /v1/lilith/adapter/safety-assessment?role=safety` request
  carrying the auth-context bearer and exact `{ userMessage, region: 'US' }`
  payload, verifies the safety interruption + 988 resources + `/profile/safety`
  link, and asserts no keep/send write happens. The same spec forces a
  safety-assessment 503 and verifies the action island fails closed before any
  keep/write. The spec reads the persisted member-scoped offering back from the
  BFF and asserts the sent intention preview is present only on that member's
  record. It also drives both buttons from the browser and reloads
  `/arete/offerings`, proving the signed-in gallery renders the live draft and
  sent rows with their member-scoped intention previews. The Save-draft / Send
  buttons are no longer inert — they drive the keep/share write end-to-end. The
  spec also forces one 503 from `/v1/arete/offerings/keep` for each footer
  action, asserts the visible alert + enabled retry state, then retries through
  the real BFF and verifies the `draft-kept` / `delivered` preview. It also
  edits recipient + occasion, asserts the browser POST body, reads the selected
  values back from `/v1/arete/offerings/sent`, and verifies the live
  `/arete/offerings` gallery row. The same spec now drives "See the C2PA
  receipt" from the browser, forwards it to the real
  `/v1/arete/offerings/receipt` BFF route, verifies the receipt id/C2PA
  manifest/cue-privacy/private-intent hashes, proves the selected recipient,
  occasion, and score feed the receipt, and asserts the raw private intention is
  not rendered in the receipt panel or direct BFF receipt JSON.
- [`apps/oshun/web/e2e/arete-coach-bff.spec.ts`](../../apps/oshun/web/e2e/arete-coach-bff.spec.ts)
  _(new)_ — **step 4 (framework-aware coach scoring) at the real BFF layer**:
  `POST /v1/arete/coach/responses` returns genuinely framework-DISTINCT guidance
  (Stoic dichotomy-of-control vs CBT thought-vs-fact vs ACT defusion, asserted
  against the engine's per-framework markers), echoes the framework, falls back
  to `general` for an unknown framework, and gates on auth (401) + a non-empty
  message (400 `missing-user-message`). Real HTTP via the `request` fixture, not
  a fixture.
- [`apps/oshun/web/e2e/arete-coach-ui.spec.ts`](../../apps/oshun/web/e2e/arete-coach-ui.spec.ts)
  _(new)_ — **step 4 at the direct browser UI layer**: authenticated
  `/domains/arete/coach` starts with an empty chat, sends the typed prompt to
  the real BFF as `{ framework: 'reflective', userMessage }`, renders the
  reflective coach response and four suggestion chips, and covers a forced 503
  as an honest recoverable coach bubble with no fake suggestions.
- [`apps/oshun/web/e2e/arete-offering-coach-preview.spec.ts`](../../apps/oshun/web/e2e/arete-offering-coach-preview.spec.ts)
  _(new)_ — **step 4 on the actual `/arete/offering` surface**: fills the
  offering intention, selects the Stoic framework, asserts the browser POST body
  sent to `/v1/arete/coach/responses`, forwards that request to the real BFF,
  renders the framework-specific dichotomy-of-control response plus four
  suggestions, then forces one 503 and verifies the recoverable error before a
  retry renders the live ACT response.
- **Coverage depth**: partial — the offering composition body now has real
  intention, dynamic scene preview copy, score-selection, material-reorder, and
  recipient/occasion controls that update the masthead summary, and keep/send
  actions are wired; see [`coverage.md`](./coverage.md).
- **Uncovered**: adjacent/below-threshold tone-band handoff, pre-submit
  score-preview safety gating, and Arete binary media signing beyond the receipt
  envelope remain open. Steps 2, 3 keep/send preflight, and 6–7 are now covered
  — the intention textarea plus recipient/occasion controls feed "Save draft" /
  "Send", the safety assessment now goes directly from the browser to the live
  Lilith BFF with the auth-context bearer, clean intentions POST
  `/v1/arete/offerings/keep` (draft-kept / delivered) per
  `arete-offering-actions.spec.ts`, and the live `/arete/offerings` gallery
  reads those private rows back, including a forced 503 + successful retry for
  both Save draft and Send, so they are no longer inert. Step 5 now also covers
  score selection and material reorder on the offering surface. The
  framework-aware coach scoring (step 4) is now covered at the **BFF layer** by
  `arete-coach-bff.spec.ts` (the route + framework-distinct scoring), at the
  direct browser UI layer by `arete-coach-ui.spec.ts`, on the actual
  `/arete/offering` surface by `arete-offering-coach-preview.spec.ts`, plus the
  engine unit spec
  `apps/oshun/web/src/components/domains/arete/__tests__/AreteCoach.test.tsx`.

## Per-view files touched by this journey

- [`customer/04-arete/arete-offering.md`](../customer/04-arete/arete-offering.md)
  — composition surface
- [`customer/04-arete/arete-coaching.md`](../customer/04-arete/arete-coaching.md)
  — coach framework selector / Sunday-letter context
- [`customer/04-arete/domains-arete-coach.md`](../customer/04-arete/domains-arete-coach.md)
  — direct conversational coach UI route
- [`customer/09-account/profile-safety.md`](../customer/09-account/profile-safety.md)
  — crisis-frame audit if pre-screen fires
- [`customer/04-arete/arete.md`](../customer/04-arete/arete.md) — entry from the
  polished Arete hub

## Cross-references

- Feature spec:
  [`V1/features.md` § Living Offerings](../../V1/features.md#living-offerings)
- Coach pipeline: [`WALKTHROUGH/matrix/coverage.md`](../matrix/coverage.md)
  finding #9 — framework-aware BFF endpoint shipped 2026-05-25
- Related journeys:
  [`crisis-aware-tone-policy.md`](./crisis-aware-tone-policy.md) (pre-screen
  step in full), [`arete-create-habit.md`](./arete-create-habit.md) (sibling
  Arete flow)
- BFF route: `apps/oshun/bff/src/routes/arete.ts`
  (`POST /v1/arete/coach/responses`)
- Offering BFF routes: `apps/oshun/bff/src/routes/domain-stubs.ts`
  (`GET /v1/arete/offerings`, `POST /v1/arete/offerings/keep`,
  `GET /v1/arete/offerings/sent`)
- Coach composer: `apps/oshun/bff/src/arete/coach-responses.ts`
- Client coach: `apps/oshun/web/src/components/domains/arete/AreteCoach.tsx`
  (BFF call + local fallback)
- Offering component:
  `apps/oshun/web/src/components/lilith/AreteOfferingWorkspace.tsx`

## Open questions / known gaps

- [x] Add gallery/read-back coverage for the draft and sent paths; the
      `/arete/offerings` gallery now reads the authenticated member's live store
      rows and `arete-offering-actions.spec.ts` asserts both private intention
      previews from the browser (2026-06-26)
- [x] Add recipient/occasion selection coverage; Section V edits now feed the
      live keep/send POST and gallery read-back (2026-06-27)
- [x] Confirm whether the intent prose passes through Lilith synchronously at
      `/arete/offering` submit time; keep/send now calls the safety-assessment
      route before any keep write (2026-06-27)
- [ ] Decide whether the pre-submit scene preview and fixture score grid should
      also be gated by Lilith before the user clicks keep/send
- [x] `/arete/offering` can pick the coach framework directly through the
      coach-preview panel; users do not need to visit `/domains/arete/coach`
      first for step-4 scoring
- [ ] Verify keep/share records honour tenant residency — no residency context
      in `composeCoachHandler`'s request body
- [x] The receipt surface is documented and wired through
      `/v1/arete/offerings/receipt`; it returns a receipt id, manifest id,
      cue-privacy hash, and private-intent hash without echoing the raw
      intention
- [ ] Bind the receipt to concrete rendered media bytes through the Living
      Scenes/Aja release pipeline; the current envelope is not proof of binary
      media signing
- [ ] Confirm the scope of the "private intent layer never leaves the
      originating user" guarantee — what aggregations are permitted?
