Journey flow#
Generated from the authored steps below — click a node to jump to that section.
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/offeringempty (the fixture currently still renders a populated draft — see Failure modes) - Returning composer —
/arete/offeringresumes a draft - Coach-mode picker — toggles framework between
general / cbt / act / stoic / reflectivebefore letting the score render - At-risk user — input prose carries a distress signal; Lilith intercepts via the Crisis-aware tone policy 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()returnsAreteOfferingData({ sceneTimeline, scoreOptions, toneSwatch, sharingRules }); BFF mapping comment marks/arete/offerings/:id(seecustomer/04-arete/arete-offering.md)- Lilith policy substrate wired into the keep/send action surface per
V1/features.md#crisis-aware-behavior - Coach BFF
POST /v1/arete/coach/responsesreachable (route inapps/oshun/bff/src/routes/arete.ts); direct chat route inapps/oshun/web/src/components/domains/arete/AreteAICoach.tsxand local fallback composer inapps/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.tsxawaitsgetAreteOffering()→<AreteOffering>inside anLWebShellwithLCustomerNav 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.mdlayout regions
2. Capture the intention#
- 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 - 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 §1–§3 applied to
Arete intent capture.
- On keep/send submit,
AreteOfferingActionscalls/v1/lilith/adapter/safety-assessment?role=safetywith the browser intention andregion: 'US'before writing/v1/arete/offerings/keep - 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/safetylink. The full durable crisis-frame activation/audit cascade remains covered bycrisis-aware-tone-policy.md, not by this client-side preflight. - If adjacent (below threshold): substrate applies the
reflectivetone band — coach scoring leans reflective even if user picked another - Clean path: substrate marks intention
safe; flow continues to the draft/send keep route
4. Coach scoring (framework selector)#
- User picks framework (CBT / ACT / Stoic / Reflective / general) directly
on
/arete/offeringthrough[data-arete-offering-coach-framework-option]; the panel exposes the selected BFF framework on[data-arete-offering-coach-panel] - Client POSTs
{ framework, userMessage }to/v1/arete/coach/responses; BFF callscomposeAreteCoachResponse(input)fromapps/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) - On non-2xx:
/arete/offeringrenders[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; coverage.md finding #9 documents this pipeline
5. Score renders into the composition#
- User can change the selected III · Score option in the 3-column
data.scoreOptionsgrid; the initial selection is seeded from the selected fixture option, and the active option exposesaria-pressed - Coach response still does not dynamically compose the
/arete/offeringscore grid; the route renders fixture-backed score options - II · Scene preview (16/9 simulated video, "▶" glyph) renders with
[data-arete-offering-scene-label]derived fromsceneTimeline[0]and[data-arete-offering-scene-preview-line]derived from the current intention - Scene timeline strip (waveform SVG, scrub head at 12%), IV · Materials 3x3
swatch grid driven by
data.toneSwatchwith tile select + drag/drop + Move earlier / Move later reorder controls, V · Sharing recipient/occasion controls +data.sharingRulesrows all render - 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/receiptto render a member-scoped receipt envelope with C2PA/cue-privacy/private-intent hashes
6. Keep — the private artifact#
- Click "Save draft" (
AreteOfferingActionsbutton); client pre-screens the browser intention through Lilith, then POSTs/v1/arete/offerings/keepwith{ recipient, occasion, action: 'draft', intention }and rendersdata-arete-offering-state="draft-kept" - Expected: write to Arete offerings store with draft state per
V1/features.md#living-offerings("kept in their gallery as a personal artifact, and re-renderable forever from the underlying score") - 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#
- Click "Send to {recipient} · {occasion}" (
AreteOfferingActionsbutton); client pre-screens the browser intention through Lilith, then POSTs/v1/arete/offerings/keepwith{ recipient, occasion, action: 'send', intention }and rendersdata-arete-offering-state="delivered" - 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 perV1/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
safeand 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/offeringsgallery 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/offeringsgallery 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#
- Save draft / Send are wired —
AreteOfferingActionsreplaced the inert footer buttons; both actions POST/v1/arete/offerings/keepand render BFF-backed status (draft-kept/delivered) - 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 todraft-keptordelivered - 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, rendersreceipt-issued, C2PA manifest id, cue-privacy hash, and private-intent hash, and does not render raw intention prose in the receipt panel - Recipient and occasion selection — Section V recipient/occasion inputs
update the send target, the browser POST body,
/v1/arete/offerings/sentreadback, and the/arete/offeringsgallery row - 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 - Score selection state — score is no longer hardcoded by index; the
client control seeds from the selected
scoreOptionsfield and updates on click - 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/keepcall - Safety preflight unavailable — a non-2xx
/v1/lilith/adapter/safety-assessmentresponse renders[data-arete-offering-error][role="alert"], leaves Save draft / Send enabled for retry, and still makes no/v1/arete/offerings/keepcall - Pre-submit composition can still preview crisis prose — the live scene
preview and fixture score grid update before the keep/send safety
preflight;
/arete/offeringstill needs a product decision on whether score preview should also be halted before action submit - 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 - Direct coach page BFF returns 5xx —
/domains/arete/coachrenders an honest "coach could not be reached" bubble and no fake suggestion chips - Framework mismatch —
CoachModeincludesgoals / habits / motivationwhich all fall through togeneralpermodeToBffFramework; user picks "Goals" expecting goal-specific scoring and gets the general lens - 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— steps 1, 2 & 5: the/arete/offeringcomposition surface — masthead + Lilith identity, the editable intention textarea (I) feeding the scene preview line, scene preview (II) + timeline with a scene label derived fromsceneTimeline[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(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=safetyBFF 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 browserPOST /v1/lilith/adapter/safety-assessment?role=safetyrequest carrying the auth-context bearer and exact{ userMessage, region: 'US' }payload, verifies the safety interruption + 988 resources +/profile/safetylink, 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/keepfor each footer action, asserts the visible alert + enabled retry state, then retries through the real BFF and verifies thedraft-kept/deliveredpreview. 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/offeringsgallery row. The same spec now drives "See the C2PA receipt" from the browser, forwards it to the real/v1/arete/offerings/receiptBFF 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(new) — step 4 (framework-aware coach scoring) at the real BFF layer:POST /v1/arete/coach/responsesreturns 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 togeneralfor an unknown framework, and gates on auth (401) + a non-empty message (400missing-user-message). Real HTTP via therequestfixture, not a fixture.apps/oshun/web/e2e/arete-coach-ui.spec.ts(new) — step 4 at the direct browser UI layer: authenticated/domains/arete/coachstarts 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(new) — step 4 on the actual/arete/offeringsurface: 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. - 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) perarete-offering-actions.spec.ts, and the live/arete/offeringsgallery 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 byarete-coach-bff.spec.ts(the route + framework-distinct scoring), at the direct browser UI layer byarete-coach-ui.spec.ts, on the actual/arete/offeringsurface byarete-offering-coach-preview.spec.ts, plus the engine unit specapps/oshun/web/src/components/domains/arete/__tests__/AreteCoach.test.tsx.
Per-view files touched by this journey#
customer/04-arete/arete-offering.md— composition surfacecustomer/04-arete/arete-coaching.md— coach framework selector / Sunday-letter contextcustomer/04-arete/domains-arete-coach.md— direct conversational coach UI routecustomer/09-account/profile-safety.md— crisis-frame audit if pre-screen firescustomer/04-arete/arete.md— entry from the polished Arete hub
Cross-references#
- Feature spec:
V1/features.md§ Living Offerings - Coach pipeline:
WALKTHROUGH/matrix/coverage.mdfinding #9 — framework-aware BFF endpoint shipped 2026-05-25 - Related journeys:
crisis-aware-tone-policy.md(pre-screen step in full),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#
- Add gallery/read-back coverage for the draft and sent paths; the
/arete/offeringsgallery now reads the authenticated member's live store rows andarete-offering-actions.spec.tsasserts both private intention previews from the browser (2026-06-26) - Add recipient/occasion selection coverage; Section V edits now feed the live keep/send POST and gallery read-back (2026-06-27)
- Confirm whether the intent prose passes through Lilith synchronously at
/arete/offeringsubmit 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
-
/arete/offeringcan pick the coach framework directly through the coach-preview panel; users do not need to visit/domains/arete/coachfirst for step-4 scoring - Verify keep/share records honour tenant residency — no residency context
in
composeCoachHandler's request body - 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?