V1 Web PWA · Journey

Journey: User report → Trust & Safety review

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 1 spec verdict: partial· 2026-05-29
9sections6 minread1table

On this page
Journey at a glance
ActorsSigned-in customerTrust & Safety adminCustomer message centerReported creator or artifact owner
Automation1 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Customer opens the shared report dialog"] s2["2. Customer submits through the same-origin r…"] s3["3. The report appears in the admin workspace"] s4["4. An authorized admin records an API decision"] s5["5. The reporter receives the decision"] s1 --> s2 --> s3 --> s4 --> s5 click s1 href "#1-customer-opens-the-shared-report-dialog" click s2 href "#2-customer-submits-through-the-same-origin-route" click s3 href "#3-the-report-appears-in-the-admin-workspace" click s4 href "#4-an-authorized-admin-records-an-api-decision" click s5 href "#5-the-reporter-receives-the-decision" s5 --> v(["verdict: partial"]) click v href "../results/user-flag-to-review.html" style v stroke:#d6a531,color:#d6a531

This is an authenticated customer-report flow, not an anonymous incident pipeline. Six customer surfaces share one report dialog and BFF intake. The admin Trust & Safety page can read the resulting queue, while the moderation decision currently happens through an admin API rather than a browser form. The reporter then receives an in-app decision message and a safety receipt.

Stage Shipped boundary
Customer intake Shared dialog on six signed-in surfaces
Report store Bounded, process-local array in the BFF
Admin browser Read-only Trust & Safety report roster
Moderation decision Privileged API with five recorded outcomes
Customer notice In-app message and /profile/safety receipt
Enforcement No content or account mutation is invoked by this route

Personas#

  • Signed-in customer — reports an item and can read only their own receipt.
  • Trust & Safety admin — reads the moderation workspace and, through the API, records a decision.
  • Customer message center — delivers the decision notice and tracks delivery, read, and click receipts.
  • Reported creator or artifact owner — contextual subject only; this route does not notify or mutate that account.

Pre-conditions#

  • The customer has an authenticated session. POST /v1/user-reports derives the reporter id from that session; there is no anonymous intake path.
  • The customer is on one of the six mounted origins:
    • Veritas lead article;
    • Veritas claim detail;
    • Nisaba passage;
    • Tara sitting;
    • Metis lesson;
    • Metis tutor.
  • The origin supplies stable target kind, id, and label metadata.
  • The admin decision caller has admin:*, admin:studio, admin:moderation, or admin:workspace:support.
  • The same BFF process remains alive if later read-back of the submitted report is required.

Steps#

1. Customer opens the shared report dialog#

  • TrustSafetyFlagButton exposes a minimum 44 px Report action with an accessible target label.
  • The dialog presents the ten canonical categories: harassment, hate, impersonation, misinformation, spam, nsfw, violence, self_harm, privacy, and other.
  • Rationale is required and constrained to 10–500 characters.
  • The form does not collect a consented context bundle, scroll position, screenshot, attachment, or appeal intent.

2. Customer submits through the same-origin route#

  • The browser posts to /api/trust-safety/flags so the access-token cookie is not exposed to client JavaScript.
  • The route forwards the request to POST /v1/user-reports.
  • The BFF validates category, target kind, target metadata, and rationale, and takes reporterId from the authenticated request context.
  • A new report starts as open, priority medium, outcome none, and unassigned.
  • The dialog renders a submission acknowledgement and the customer can read the receipt from GET /v1/user-reports/mine.
  • No classifier assigns P0–P3 severity, detects brigading, or routes a crisis-trained reviewer.

3. The report appears in the admin workspace#

  • recordCustomerUserReport prepends the record to the admin moderation workspace's userReports projection.
  • /trust-safety renders that roster with the report's target, reporter, category, rationale, priority, status, and outcome.
  • The array is module-level, bounded, and process-local. It is not a durable case-management ledger.
  • The admin page is read-only for these rows. It has no assignment, evidence, decision, second-review, or appeal form.
  • Customer reports do not become INC-* records in the Admin Cockpit incident workspace.

4. An authorized admin records an API decision#

  • POST /v1/admin/user-reports/:reportId/decision accepts one of five outcomes: - warned; - content_removed; - account_suspended; - policy_referral; - dismissed_no_action.
  • The decision reason is constrained to 10–1000 characters.
  • The report is updated in the process-local roster and a trust_safety.user_report.decided admin audit event is recorded.
  • Outcome names are moderation receipts, not proof of enforcement: content_removed does not remove an artifact and account_suspended does not change an account in this route.
  • There is no browser decision control, two-reviewer gate, repeat-offender escalation, takedown cascade, or optimistic-concurrency token.

5. The reporter receives the decision#

  • The decision route emits an in-app incident message for the original reporter.
  • The message deep-links to /profile/safety?reportId=<id>.
  • The reporter sees an actioned receipt on /profile/safety and the notification in /messages.
  • Following the deep link records a clicked receipt; read state remains a separate action.
  • Another customer cannot read the reporter-scoped receipt.
  • No external push, email, reported-creator notice, or appeal is emitted.

Post-conditions#

  • The authenticated reporter has a report id, current status, and eventual in-app decision receipt.
  • The Trust & Safety roster and audit search expose the recorded decision for the lifetime of the BFF process.
  • The decision is an administrative classification only; downstream content removal, account suspension, referral handling, and appeals remain outside this implementation.
  • No documentation claim treats the read-only admin page as a complete moderation cockpit.

Failure modes#

  • Anonymous submit — the BFF rejects the request; reporter identity cannot be supplied by the body.
  • Invalid target or category — validation fails before the report is recorded.
  • Rationale outside 10–500 characters — intake is blocked.
  • Missing admin scope — the decision route rejects the caller.
  • Unknown report id or invalid outcome — no report is changed and no decision audit event is written.
  • BFF restart — the module-level report array and its decisions disappear.
  • Outcome mistaken for enforcement — the route records content_removed or account_suspended but invokes no artifact or account mutation.
  • External delivery expected — only the in-app message-center path exists.
  • Report from an unsupported surface — no shared Report affordance is mounted there.

E2E coverage#

  • apps/oshun/web/e2e/user-flag-to-review.spec.ts verifies all six mount points, target metadata, dialog accessibility, the ten-category picker, 10–500 character rationale gate, one real submission, admin workspace read-back, privileged API decision, admin audit search, reporter isolation, the actioned safety receipt, message deep link, and clicked receipt.

Coverage is deep for the current single-process intake and receipt chain. It does not browser-drive a moderation decision because no such admin form exists, and it does not prove durable storage, severity routing, enforcement, external delivery, or appeal.

Per-view files touched by this journey#

Cross-references#

  • trust-safety-voice-abuse-response.md — a separate admin policy-response journey.
  • incident-handling-admin.md — the dedicated incident cockpit; customer reports do not enter that model.
  • apps/oshun/web/src/components/TrustSafetyFlagButton.tsx — shared dialog and validation UX.
  • apps/oshun/bff/src/routes/user-reports.ts — customer intake and receipt routes.
  • apps/oshun/bff/src/admin/routes/user-reports.ts — privileged decision route.
  • apps/oshun/bff/src/admin/state.ts — bounded, process-local roster projection.

Open questions#

  • Which durable case store should replace the module-level report array?
  • Which admin surface will own assignment, evidence review, decisions, and second-review gates?
  • Which service turns recorded decision outcomes into idempotent content and account enforcement?
  • How will severity, crisis escalation, brigading controls, and repeat-offender rules be applied?
  • Which decisions are appealable, and where does the reporter or reported creator enter that flow?
  • Which external channels should supplement the in-app message?