docs(safety): record USER_SAFETY step 3, Module-Rust's chat lines
Rewrites §4.2 to match what was built: server/safety.js, the in-query feed filter, rust_events.hidden_at / hidden_by, the staff hide and unhide routes on the line's own menu, and what happens when core's safety reads fail. Records §7 step 3 as built and walked (28 API rows and a browser pass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Report, block and hide — user safety on the website and in the app
|
||||
|
||||
**Status:** plan of record, 2026-10-29. Step 2 (core) built 2026-10-30 (§7); the rest not yet.
|
||||
**Status:** plan of record, 2026-10-29. Steps 2 (core) and 3 (Module-Rust, Integration Kit) built
|
||||
2026-10-30 (§7); the rest not yet.
|
||||
Each phase stops for the org lead before the next one starts. Built after account deletion
|
||||
([`ACCOUNT_DELETION.md`](ACCOUNT_DELETION.md)), which is why it is `MODULE_API` 1.21.0. §4 was
|
||||
amended by S11-S14 (§5) when the core was built.
|
||||
@@ -111,20 +112,39 @@ staff's.
|
||||
|
||||
### 4.2 Module-Rust
|
||||
|
||||
All of it is in `server/safety.js`, beside the events route that uses it.
|
||||
|
||||
- `registerReportTargets([{ id: 'rust.chat', … }])` with `snapshot` (the speaker's name and Steam
|
||||
ID, the line, server, time, and the linked account if there is one; null when the viewer may not
|
||||
see presence on that server) and `hide`/`unhide`. `registerGameIdentities([{ kind: 'steam', …
|
||||
}])` with `ownersOf` reading `rust_account_links`.
|
||||
- **Hide for everyone** sets a flag on the `rust_events` row (`hidden_at`, `hidden_by`). The events
|
||||
endpoint leaves hidden chat lines out for everybody except staff, who get them marked.
|
||||
- **The events endpoint filters per reader** (S11) with `ctx.safety.forViewer`: a line is left out
|
||||
when its speaker's linked account is blocked, or, for an unlinked speaker, when their Steam ID
|
||||
is; and when the reader hid it. Signed-in readers get `canBlock` on each chat line (S13): false
|
||||
when the speaker's linked account is staff (`ctx.safety.staffAmong`) or is the reader.
|
||||
- **The live feed's chat lines gain a menu**: *Report*, *Hide for me*, *Block player* (where
|
||||
`canBlock`), and for staff *Hide for everyone*. They call core's `/player/reports`,
|
||||
`/auth/me/blocks` and `/auth/me/hidden` through the shared request primitive.
|
||||
ID, the line, server, channel, wipe, time, and the linked account if there is one; null when the
|
||||
viewer may not see presence on that server, or the line is hidden for everyone and the viewer is
|
||||
not staff) and `hide`/`unhide`. `registerGameIdentities([{ kind: 'steam', … }])` with `ownersOf`
|
||||
reading `rust_account_links`. A `rust.chat` id is a `rust_events` id whose kind is `player.chat`;
|
||||
any other id is not one, so reporting a death by its id is a 404.
|
||||
- **Hide for everyone** sets a flag on the `rust_events` row (`hidden_at`, `hidden_by`, added by
|
||||
`ALTER … ADD COLUMN IF NOT EXISTS` so an existing install gets them). The events endpoint leaves
|
||||
hidden chat lines out for everybody except staff, who get them with `hidden: true`. It is set from
|
||||
core's Reports queue, or from the line's own menu through `POST /admin/rust/chat/:id/hide`, and
|
||||
cleared only there, through `POST /admin/rust/chat/:id/unhide` (admins and moderators, the two
|
||||
roles the queue is open to; both written to the activity log as `rust.chat.hide` /
|
||||
`rust.chat.unhide`). Hiding a line the retention sweep has already pruned does nothing, and the
|
||||
report is still actioned.
|
||||
- **The events endpoint filters per reader** (S11) with `ctx.safety.forViewer`, **in the query**,
|
||||
so a page of 100 lines is still 100 long: a line is left out when its Steam ID is linked to an
|
||||
account the reader blocks, or is one of the reader's own Steam-ID blocks (which core answers only
|
||||
for ids still unlinked, S14); and when the reader hid it. Only `player.chat` rows are filtered: a
|
||||
block hides what someone says, and their deaths and connects stay in the feed. Signed-in readers
|
||||
get `canBlock` on each chat line (S13): false when the line has no Steam ID, or the speaker's
|
||||
linked account is staff (`ctx.safety.staffAmong`) or is the reader. If the reader's blocks cannot
|
||||
be read, the feed is served unfiltered for them with a warning in the log, rather than failed;
|
||||
if who is staff cannot be read, every line says `canBlock: false`.
|
||||
- **The live feed's chat lines gain a menu** (`components/ChatMenu.jsx`, a `⋯` at the end of each
|
||||
line): *Report* (the six reasons and an optional detail), *Hide for me*, *Block player* (where
|
||||
`canBlock`, after a confirmation that says the player is not told), and for staff *Hide for
|
||||
everyone* or, on a hidden line, *Show to everyone again*. A signed-out reader gets *Sign in to
|
||||
report or block*. They call core's `/player/reports`, `/auth/me/blocks` and `/auth/me/hidden`
|
||||
through the shared request primitive, and re-read the feed after anything that changes it.
|
||||
- On the demo, all of this stays **open**: it is what a Play reviewer tests, and the wipe resets it.
|
||||
None of these routes is in `demoLocks`.
|
||||
|
||||
### 4.3 Android app (chat lines only, S4)
|
||||
|
||||
@@ -180,6 +200,29 @@ Play declarations gain "user-generated content: collected". M21 says so.
|
||||
(`rust.chat`, `ctx.safety`) is exercised by unit tests here and walks with step 3.
|
||||
3. **Module-Rust** (`edge`): §4.2 plus the core-ref pin. One PR. **Integration-kit**: the re-pin and
|
||||
a paragraph on `reportTargets`. One PR.
|
||||
|
||||
**Built 2026-10-30** as Module-Rust `feature/user-safety` (`coreApi` ^1.21.0, pinned to the
|
||||
website#233 merge) and Integration-kit `docs/core-1.21.0`. Server and client suites, the import,
|
||||
bundle, swagger and engagement checks, and the frozen route manifest against a core at the pin
|
||||
(two new routes, all documented) are green. Walked against a throwaway database on the local
|
||||
MariaDB with a core on its own port and this branch's module installed, with chat lines and Steam
|
||||
links written straight into the tables (no game needed), 28 rows over bearer logins: a
|
||||
signed-out reader got every line and no `canBlock`; a member's `canBlock` was true for unlinked
|
||||
and linked players and false for their own line and a moderator's; a report reached the queue
|
||||
with the line's snapshot; a line on a server whose presence is staff-only, a death's id and a
|
||||
missing id were all 404; blocking an unlinked speaker made a Steam-ID block and took their chat
|
||||
(not their death) out of that member's feed only; blocking a linked speaker made an account
|
||||
block; blocking a moderator was `403 staff`; *Hide for me* hid one line for one member; a
|
||||
Steam-ID block whose id was then linked read back as an account block and kept the line out
|
||||
(S14); *Hide for everyone* from the queue actioned the report, took the line out for members and
|
||||
signed-out readers, showed it to staff marked, and made it unreportable; the line's own staff
|
||||
route unhid and hid it (a player got 403, a missing line 404) and wrote the activity log;
|
||||
unblocking and *Show them all again* brought the lines back. Then in the browser: the `⋯` menu
|
||||
signed out (*Sign in to report or block*), as a member (Report with the duplicate-report
|
||||
refusal and a fresh report, Block with its confirmation, no Block on their own or the
|
||||
moderator's line), *Account → Blocked* listing the Steam-ID block and both account blocks, and as
|
||||
a moderator *Hide for everyone* / *Show to everyone again* with the *Hidden by staff* marker and
|
||||
the Reports queue showing both chat-line reports from their snapshots. No console errors.
|
||||
4. **Android** (`edge`): §4.3. One PR.
|
||||
5. **A walk** on the demo after the next wipe: report, hide and block, each on the web and in the
|
||||
app; *Hide for everyone* from the queue; a blocked Steam ID that later links to an account stays
|
||||
|
||||
Reference in New Issue
Block a user