---
path: /profile/safety
surface: customer
domain: account
auth: signed-in
source: apps/oshun/web/src/app/profile/safety/page.tsx
status: walked
last_walked: >
  2026-06-26 submitted user-report receipts — signed-in reporter creates a live
  /v1/user-reports record, moderator decisions it through
  /v1/admin/user-reports/:reportId/decision, and /profile/safety renders the
  actioned receipt, including the /messages decision-notification deep-link
  return to /profile/safety?reportId=<id>, in user-flag-to-review.spec.ts.
  2026-06-26 public scene report receipts — signed-in reporter submits
  /scene/<shortCode>/report with the real bearer, reads GET
  /v1/living-scenes/public-reports/mine, sees the open receipt on
  /profile/safety, operator resolves it, and the page shows resolved status plus
  note in public-scene-abuse-report.spec.ts. 2026-06-25 real-dev-infra route
  journey — live operator activation POST
  /v1/admin/safety/crisis-frame/activate, Redis-worker durable projection,
  member readback GET /v1/safety/crisis-frames/mine, empty state, anonymous
  redirect, stillness, re-entry accept/decline, check-in
  deliver/dismiss/opt-out, and axe verified by
  profile-safety-crisis-recovery.spec.ts. 2026-06-29 offline replay addendum —
  /profile/safety is an exact service-worker shell navigation; a successful live
  frame load writes a last-frame snapshot, and Chromium E2E proves offline
  cached-document replay restores the audit-visible frame plus the member's
  locally accepted re-entry choice without the generic fallback. 2026-06-30
  timeline addendum — the full cross-surface /v1/safety/crisis-frames/mine frame
  array now renders as an expandable crisis-frame timeline and is real-BFF
  covered by profile-safety-crisis-recovery.spec.ts. 2026-06-30 resource-link
  addendum — the surfaced `us-988` and audit-platform resources are actionable
  links, and the real-BFF profile-safety flow asserts
  `lilith_safety_resource_opened` telemetry.
---

# Profile · Safety Record

## Purpose

"Audit-platform-visible record of recent crisis frames, recovery steps,
resources surfaced, submitted Trust & Safety reports, and public-scene report
receipts." The server route mounts the `ProfileSafetyJourney` client
composition, which reads the signed-in member's active crisis-frame projection,
submitted user-report receipts, and public-scene report receipts from the BFF,
then derives the local recovery controls from the Lilith crisis recovery
library.

The page must never show an example crisis event. With no durable frame, it
renders an honest empty record.

## Entry points

- `/profile` sub-section deep link (highlightPath = safety)
- Profile settings panel "Safety" row → `/profile/safety`
- `/messages` report-status decision notification deep link →
  `/profile/safety?reportId=<id>`
- Crisis-frame follow-up links (post-incident push or in-product notification) —
  product entry point still to verify
- Direct URL / bookmark — yes (auth required; anonymous users redirect before
  the safety page renders)

## Layout regions

`page.tsx` is `<ProfileSafetyJourney />` with no route chrome. The component
renders a constrained work surface with:

- **Header** — back link to `/profile`, H1 "Safety record", and a one-sentence
  explanation that the view mirrors the audit platform's crisis-frame record.
- **What happened** (`data-profile-safety-what-happened`) — frame type, opened
  time, closed time, operator review SLA, and a redacted excerpt. The page maps
  the BFF crisis taxonomy to Lilith recovery taxonomy before building this view:
  `suicidal-ideation → suicide-ideation`,
  `self-harm-imminent → active-self-harm`,
  `violence-to-others-credible → violence-toward-others`,
  `child-protection-signal → child-safety`,
  `domestic-violence-signal → interpersonal-violence`, and
  `medical-emergency → acute-panic`.
- **Report receipts** (`data-profile-safety-report-receipts`) — two receipt
  lanes:
  - **Submitted reports** (`data-profile-safety-user-report-receipts`) reads
    signed-in Trust & Safety reports from `GET /v1/user-reports/mine`. Each row
    has `data-profile-safety-user-report-row=<reportId>`,
    `data-profile-safety-user-report-status=open|triaging|actioned|dismissed`,
    and `data-profile-safety-user-report-outcome=<AdminReportOutcome>`, then
    shows target label, category, submitted/updated timestamps, rationale, and a
    Trust & Safety reviewed marker for actioned reports.
  - **Public scene reports** (`data-profile-safety-public-report-receipts`)
    reads signed-in public-scene abuse reports filed by this member, newest
    first. Each row has `data-profile-safety-report-row=<reportId>` and
    `data-profile-safety-report-status=open|resolved|dismissed`, links back to
    `/scene/<shortCode>`, shows reason/detail, and shows the operator note when
    the report is terminal.
- **Crisis-frame timeline** (`data-profile-safety-timeline`) — an expandable
  chronological list of every active BFF frame marker projected into the
  contemplative surfaces (`psyche`, `lilith-video`, `tara`, `iris`,
  `assistant`). Each row carries `data-profile-safety-timeline-row`,
  `data-profile-safety-timeline-surface=<surface>`, and an expanded
  `data-profile-safety-timeline-detail` block with frame ID, region, synthesis
  halt, and memory-write suspension.
- **Stillness window** (`data-profile-safety-stillness`) — explains the minimum
  stillness period and lists non-safety surfaces with
  `data-profile-safety-surface=<surface>` plus `data-suspended=true|false`.
- **Re-entry** (`data-profile-safety-reentry`) — shows a disabled "Show re-entry
  prompt" button before the window closes, then lets the member accept into the
  conservative re-entry home or decline into idle-safe routing.
- **Soft check-ins** (`data-profile-safety-checkins`) — 24h and 7d rows with
  scheduled/delivered/dismissed/opt-out state and an opt-out control.
- **Resources surfaced** (`data-profile-safety-resources`) — actionable resource
  links currently including `us-988` → `tel:988` and `audit-platform` →
  `#profile-safety-record`, plus the operator queue lane. Links expose
  `data-profile-safety-resource-link=<resourceId>` and
  `data-profile-safety-resource-destination=<href>`.

## States

- [x] **Loading** — component renders "Loading your record..." while auth/BFF
      read is pending.
- [x] **Empty** — real BFF read returns no frames; page renders
      `data-profile-safety-empty` and does not show the removed fabricated
      Renata/acute-grief sample.
- [x] **Populated (single active frame)** — live operator activation projects a
      durable frame and `/profile/safety` renders the interactive record.
- [x] **Recent event** — current surface shows the first active frame returned
      by `/v1/safety/crisis-frames/mine`; all surfaces share the same frame ID
      for a single activation.
- [x] **Error / unavailable** — non-2xx fetch or missing auth token renders
      `data-profile-safety-unavailable` and refuses to show an example record.
- [x] **Submitted report receipts empty** — real BFF read returns no signed-in
      user reports; page renders `data-profile-safety-user-report-empty`.
- [x] **Submitted report receipts populated** — signed-in
      `POST     /v1/user-reports` row renders from `GET /v1/user-reports/mine`
      with open or terminal status and decision outcome.
- [x] **Submitted report receipts unavailable** — non-2xx user-report receipt
      fetch renders `data-profile-safety-user-report-unavailable`; the page does
      not fabricate report history.
- [x] **Public scene report receipts empty** — real BFF read returns no
      signed-in public-scene reports; page renders
      `data-profile-safety-report-empty`.
- [x] **Public scene report receipts populated** — signed-in public-scene report
      row renders from `GET /v1/living-scenes/public-reports/mine` with open or
      terminal status and the operator note.
- [x] **Public scene report receipts unavailable** — non-2xx receipt fetch
      renders `data-profile-safety-report-unavailable`; the page does not
      fabricate report history.
- [x] **Anonymous user** — route redirects to `/welcome`; the safety page does
      not render.
- [x] **Populated timeline** — the page renders the full active BFF frame array
      as `data-profile-safety-timeline`; the first row is current and every
      projected surface marker can be expanded for frame metadata.
- [x] **Offline** — after a successful authenticated live frame load, the route
      stores a compact last-frame snapshot beside the frame-keyed recovery
      state; `/profile/safety` is an exact service-worker shell navigation, so a
      later offline navigation replays the cached document, restores the real
      audit-visible frame and local re-entry choice, marks
      `data-profile-safety-offline-snapshot="true"`, and does not show the
      generic offline fallback.

## Interactions

- [x] **Re-entry prompt unlock** — before stillness closes, "Show re-entry
      prompt" is disabled; after clock advance, it becomes actionable.
- [x] **Accept re-entry** — routes to
      `data-profile-safety-reentry-status="home"` and names the
      `contemplative-strict` tone band.
- [x] **Decline re-entry** — routes to
      `data-profile-safety-reentry-status="idle"` while keeping the audit card
      and `us-988` resource visible.
- [x] **24h check-in deliver/dismiss** — due row exposes "Deliver now", then
      "Dismiss", and the row state updates to `delivered` then `dismissed`.
- [x] **7d check-in opt-out** — "Opt out of soft check-ins" changes remaining
      scheduled rows to `data-state="opt-out"` and disables itself.
- [x] **Timeline entry expansion** — each timeline marker button toggles an
      expanded frame metadata block with frame ID, region, synthesis halt, and
      memory-write suspension.
- [x] **Resource link** — surfaced resource rows are anchors and emit
      `lilith_safety_resource_opened` with `resourceId`, `resourceKind`,
      destination, and frame ID.
- [ ] **Export** — no export control exists on this surface.

## Data & contracts

- **Reads**: `GET /v1/safety/crisis-frames/mine` from
  `apps/oshun/bff/src/safety/my-crisis-frames-route.ts`. The route requires the
  signed-in member bearer, sets `cache-control: no-store`, and returns
  `{ generatedAt, frames[] }`, where each frame includes `frameId`, `surface`,
  `kind`, `region`, `activatedAtIso`, `haltSynthesis`, and
  `suspendMemoryWrites`.
- **Submitted report receipt reads**: `GET /v1/user-reports/mine` from
  `apps/oshun/bff/src/routes/user-reports.ts`. The route requires the signed-in
  member bearer and filters live customer-submitted reports by
  `reporterId === authContext.userId`. Response rows are `AdminUserReportRecord`
  receipts with `reportId`, reporter/target metadata, `category`, `rationale`,
  `status`, `outcome`, reviewer fields, optional `relatedContentItemId`,
  `submittedAt`, and `lastUpdatedAt`.
- **Public scene report receipt reads**:
  `GET /v1/living-scenes/public-reports/mine` from
  `apps/oshun/bff/src/routes/my-abuse-reports.ts`. The route requires the
  signed-in member bearer and filters `livingSceneAbuseReportStore` by
  `reporterUserId === authContext.userId`. Response rows include `reportId`,
  `shareId`, `shortCode`, `reason`, `detail`, `createdAtUnixSeconds`,
  `deepLinkPath`, `status`, and terminal `actionedAtUnixSeconds` /
  `resolutionNote` when present; operator identity is intentionally omitted.
- **Write source for receipts**:
  `POST /v1/living-scenes/public/:shortCode/report` from
  `apps/oshun/bff/src/routes/living-scenes.ts`. Anonymous reports keep
  `reporterUserId: null`; signed-in reports include an Authorization bearer and
  the BFF stamps the validated subject as `reporterUserId`.
- **Write source for submitted reports**: `POST /v1/user-reports` from
  `apps/oshun/bff/src/routes/user-reports.ts`; moderator decisions are written
  by `POST /v1/admin/user-reports/:reportId/decision`.
- **Write source for frames**: operator-scoped
  `POST /v1/admin/safety/crisis-frame/activate` publishes the crisis-frame
  event; the real worker projects durable markers per contemplative surface.
- **Local recovery state**: the page builds a `StillnessWindow`, `ReentryFlow`,
  `CheckInSchedule`, and `IncidentRecord` from the active frame using
  `@oshun/persona-policy-lilith`. Member choices are persisted under
  `oshun-profile-safety-v1:<frameId>`. The page also writes
  `oshun-profile-safety-v1:last-frame` after a live frame read so
  browser-offline route replay can recover the frame identity needed to read the
  frame-keyed local recovery choices.
- **Server writes from this page**: none. Re-entry and check-in changes are
  local recovery-state choices; they are not currently posted back to the BFF.
- **Telemetry**: resource opens dispatch `lilith_safety_resource_opened` with
  `surface: 'profile_safety'`, `sourcePath: '/profile/safety'`, `resourceId`,
  `resourceKind`, `destination`, and the active `frameId`. The customer taxonomy
  records this under canonical `oshun.safety.resource_opened`.
- **Auth**: `AuthProvider` supplies the access token; the page waits for
  authenticated status before issuing the BFF read.
- **Metadata**: `title: 'Safety record'`,
  `description: 'Audit-platform-visible record of recent crisis frames, recovery steps, resources surfaced, and report receipts.'`,
  `robots: { index: false, follow: false }`,
  `alternates.canonical: '/profile/safety'`.

## E2E coverage

- [`apps/oshun/web/e2e/profile-safety-crisis-recovery.spec.ts`](../../../apps/oshun/web/e2e/profile-safety-crisis-recovery.spec.ts)
  - live no-frame empty state from `/v1/safety/crisis-frames/mine`
  - live operator activation → durable projection → member page readback
  - full cross-surface timeline from the real BFF `frames[]` projection,
    including expandable assistant-frame metadata
  - stillness suspension/lift across all rendered non-safety surfaces
  - re-entry accept and decline paths
  - surfaced `us-988` link target plus `lilith_safety_resource_opened` telemetry
    from the live safety record
  - 24h deliver/dismiss and 7d opt-out
  - real service-worker offline cached-document replay of the last live frame
    and locally accepted re-entry choice without generic fallback markers
  - anonymous redirect before render
  - shared axe gate via `./fixtures`
- [`apps/oshun/web/e2e/public-scene-abuse-report.spec.ts`](../../../apps/oshun/web/e2e/public-scene-abuse-report.spec.ts)
  - signed-in reporter submits `/scene/<shortCode>/report`
  - direct BFF receipt read returns the report as `open`
  - `/profile/safety` shows the report receipt row
  - operator resolve persists terminal status and note
  - `/profile/safety` readback shows `resolved` and the operator note
- [`apps/oshun/web/e2e/user-flag-to-review.spec.ts`](../../../apps/oshun/web/e2e/user-flag-to-review.spec.ts)
  - signed-in reporter creates a live Trust & Safety report through the real BFF
  - moderator records a `content_removed` decision through the real admin BFF
  - moderator decision emits a real in-app customer-message notification and the
    `/messages` deep-link follow returns to `/profile/safety?reportId=<id>`
  - direct `GET /v1/user-reports/mine` read-back proves reporter isolation and
    terminal status
  - `/profile/safety` shows the submitted-report receipt row as `actioned` with
    `content_removed`

## Cross-references

- Parent: [`profile.md`](./profile.md)
- Sibling: [`profile-memory.md`](./profile-memory.md)
- Journey:
  [`../../journeys/crisis-aware-tone-policy.md`](../../journeys/crisis-aware-tone-policy.md)
- Journey:
  [`../../journeys/public-scene-abuse-report.md`](../../journeys/public-scene-abuse-report.md)
- Journey:
  [`../../journeys/user-flag-to-review.md`](../../journeys/user-flag-to-review.md)
- Lilith safety policy: `V1/features.md#lilith-persona-policy` and
  `V1/features.md#persona-taxonomy-tone-band-catalog-and-crisis-recovery-journey`
- Component source:
  - `apps/oshun/web/src/app/profile/safety/ProfileSafetyJourney.tsx`

## Open questions / known gaps

- [x] Resource links and resource-open telemetry are covered from the real
      profile-safety flow.
- [ ] Recovery choices are local-only; decide whether accept/decline,
      check-in-dismiss, and opt-out should be audit-visible BFF writes.
- [ ] Confirm what the "audit-platform-visible" framing means for operator
      parity: whether the operator audit team sees the same derived recovery
      state or only the durable frame marker.
- [x] Visited offline replay is proven for the safety record: after a live frame
      load, `/profile/safety` can replay the cached document and
      `oshun-profile-safety-v1:last-frame` snapshot while the browser is
      offline. A first-ever offline visit with no cached document remains
      outside the offline contract and cannot show member data.
