From 90ba3370471f839af75e776f5de1db5788061fa1 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Fri, 30 Oct 2026 09:04:29 -0500 Subject: [PATCH] docs(safety): record USER_SAFETY step 3, Module-Rust's chat lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- website/USER_SAFETY.md | 69 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/website/USER_SAFETY.md b/website/USER_SAFETY.md index f26ca55..e2d27d8 100644 --- a/website/USER_SAFETY.md +++ b/website/USER_SAFETY.md @@ -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 -- 2.49.1