trust-safety.mdmessages.mdveritas-home.mdtara-sit.mdnisaba-passage.mdmetis-lesson.mdprofile-safety.mdJourney flow#
Generated from the authored steps below — click a node to jump to that section.
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-reportsderives 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, oradmin: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#
-
TrustSafetyFlagButtonexposes 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, andother. - 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/flagsso 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
reporterIdfrom the authenticated request context. - A new report starts as
open, prioritymedium, outcomenone, 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#
-
recordCustomerUserReportprepends the record to the admin moderation workspace'suserReportsprojection. -
/trust-safetyrenders 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/decisionaccepts 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.decidedadmin audit event is recorded. - Outcome names are moderation receipts, not proof of enforcement:
content_removeddoes not remove an artifact andaccount_suspendeddoes 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/safetyand 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_removedoraccount_suspendedbut 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#
- customer/05-veritas/veritas-home.md — Veritas lead report origin.
- customer/06-tara/tara-sit.md — Tara sitting report origin.
- customer/07-nisaba/nisaba-passage.md — Nisaba passage report origin.
- customer/08-metis/metis-lesson.md — Metis lesson and tutor report origins.
- customer/09-account/profile-safety.md — reporter-scoped decision receipt.
- customer/02-home-discovery/messages.md — in-app decision notification.
- ADMIN_WALKTHROUGH/trust-safety.md — read-only admin Trust & Safety roster.
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?