# moderation: blocks & reports

safety primitives layered on top of the friendship graph. added ahead of any
public/discovery surface.

## block model

- a block is a **directional edge** (`user_blocks`, blocker → blocked) whose
  effects are enforced **bidirectionally**. see `api/src/services/blocks.ts`.
- blocking is **non-destructive to shared trails**: accepted collaborations
  survive, but each user's content is filtered from the other (see below).
  only pending friend/invite edges are torn down.

### what a block severs (enforcement paths)

most content is already gated by friendship, so removing the friendship on
block cuts it for free. the explicit checks:

| path                                                        | file                                                             | mechanism                                                                |
| ----------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ |
| friend requests                                             | `routes/friends.ts`                                              | `isBlockedBetween` → 403                                                 |
| user search                                                 | `routes/search.ts`                                               | `notBlockedByViewer` filter                                              |
| profile view                                                | `routes/users.ts`                                                | see asymmetry below                                                      |
| dm inbox                                                    | `services/dms.ts`                                                | `notBlockedByViewer` on the pair                                         |
| trail posts                                                 | `services/trails.ts` `listTrailPosts`                            | `notBlockedByViewer`                                                     |
| trail reactions                                             | `services/trails.ts` `loadReactionsByCrumb`, `listCrumbReactors` | `notBlockedByViewer`                                                     |
| friendship, feed, recs, profile sub-lists, dm send, invites | —                                                                | cut transitively (block removes friendship, which those already require) |

### profile asymmetry

- **they blocked me** → the profile 404s (existence hidden).
- **i blocked them** → the profile stays visible with `friendshipStatus:
'blocked'` and no content, so the viewer can unblock. this is the only place
  the `blocked` status surfaces (blocked users never appear in lists).

## report model

- write-only for now: `reports` rows persisted for later human review, no admin
  surface (`routes/reports.ts`).
- subject: `user | crumb | trail` + uuid. reason: `spam | harassment |
inappropriate | other` + optional free-text detail.

## mobile

- block / unblock / report live in the profile header options menu
  (`hooks/use-profile-actions.ts`); the blocked state renders an unblock
  affordance in `profile/[userId]/index.tsx`.
