docs: plan report/block/hide, decide M8's open items, plan demo content
- website/USER_SAFETY.md (new): report, block and hide on the website and in the app, S1-S5 decided by the org lead, five questions open. - android/PLAN.md: M8's three open items decided (keep the rating and add report/block/hide; conservative data safety; UnifiedPush on specialUse with restart on boot), the order to the cutover, and M21 (Teams and the team forum in the app, planned, not scheduled). - website/DEMO_MODE.md: DM32 and section 11, the demo content plan (two seed passes; moderation, Teams/forum/reports, event history, Rust stats). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -498,6 +498,7 @@ down.
|
||||
| DM29 | (step-5 walk, W2, 2026-10-28) `SECRET_WORDS` also hides harmless fields such as "Allow Master Key" and "TC Auth Only". Narrow it? | **No, leave it as is** |
|
||||
| DM30 | (step 6, 2026-10-28) How does runicgateway.com `/beta` publish the logins? | **Live from the demo.** `/beta` reads this site's `GET /public/settings` `demo.logins` on its server, caches it for a minute, and links to `/account/login` when the demo does not answer (runicgateway.com D82) |
|
||||
| DM31 | (step 6) runicgateway.com's "See it running" links were UO routes, which 404 here. What do they do? | **Per game.** A deep link names its game, and that site's mounted `demoGames` (`["rust"]` for this demo) says which games' links show. Rust's point at `/rust` and `/rust/servers/demo?tab=…`, so that site now quotes the fixture's server id `demo` (runicgateway.com D83) |
|
||||
| DM32 | (2026-10-29) The moderator tour shows empty queues, and other surfaces are empty too (§11). Which get seeded? | **All four of §11's groups:** moderation history, Teams with their forum and reports, event history with the calendar, and Rust player stats |
|
||||
|
||||
DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is
|
||||
setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for
|
||||
@@ -647,3 +648,71 @@ through that proxy.
|
||||
locks). The org lead types the password, ticks the full-access attestation and saves. The closed
|
||||
track does not exist yet (Test and release shows *Get started* for closed and internal testing), so
|
||||
there is no opt-in URL for `betaOptInUrl`, and `/beta`'s first gate stays shut.
|
||||
7. **Demo content** (DM32, §11): the seed's two passes and the rig's clans. Planned 2026-10-29.
|
||||
One PR in `website` (core seed, the wipe's second pass), one in `Module-Rust` (`onDemoSeed`
|
||||
stats and feed lines, into `edge`), a new baseline, and a walk of every surface §11 names after
|
||||
the next wipe.
|
||||
|
||||
## 11. Demo content (DM32)
|
||||
|
||||
**Why.** A read-only count of the live demo on 2026-10-29 found these surfaces empty after a wipe:
|
||||
every moderation queue (recent actions, filter hits, spam hits, member events, appeals, reports),
|
||||
Teams (public, the player's own, staff's review queue), event runs and history, the public calendar
|
||||
(the three seeded events are never scheduled), `demo-player`'s inbox, and on the Rust side the
|
||||
leaderboard, wipes, clans and the events feed. Only the four demo accounts exist. A reviewer touring
|
||||
as `demo-moderator` finds nothing to moderate.
|
||||
|
||||
### 11.1 Two seed passes
|
||||
|
||||
Some of it can be written the moment the site is up. The rest depends on the rig: Teams are
|
||||
**module-authoritative** and re-synced every 15 minutes (`TEAMS.md`), so a Team written straight
|
||||
into core would be archived by the next sync, and the Rust stats need the wipe the rig is on. So:
|
||||
|
||||
- **Pass 1, `seed:demo`, as today**: accounts, settings, content, events, and the new core rows
|
||||
below that need nothing from the rig.
|
||||
- **Pass 2, `seed:demo --after-rig`**, a new step in `demo-wipe.yml` after the rig reports in and
|
||||
the first Teams sync has run: Team forum content, reports, Rust stats and feed lines. It waits for
|
||||
the Teams it names, with a time limit, and fails the wipe if they never appear, as the rig smoke
|
||||
check does.
|
||||
|
||||
The rig's wipe id comes from the restored world save, so it is the same on every wipe
|
||||
(`w-20261005T165054Z` today). Pass 2 reads it from the server state rather than the fixture, so a
|
||||
new baseline with a new map still works.
|
||||
|
||||
### 11.2 What each group seeds
|
||||
|
||||
- **Moderation history** (pass 1, core). Discord-shaped rows for six invented members: a few
|
||||
`mod_actions` (warn, timeout, ban), `warnings`, `filter_hits` and `spam_hits`, join and leave
|
||||
`member_events`, staff `mod_notes` (one admin-only), and three `appeals`: one pending to claim,
|
||||
one under review, one resolved. Times are relative to the seed, so the 24 h / 7 d / 30 d counts
|
||||
are never zero. There is no bot on the demo, so approving an appeal records the reversal as
|
||||
failed. That is the shipped behaviour when the bot is down, and the appeal still closes.
|
||||
- **Teams, forum and reports.** Two or three clans **in the rig's baseline** (the Clans plugin's
|
||||
data, which already reports an empty board), with stand-in members and `demo-player`'s stand-in
|
||||
Steam ID in one, so sync brings them in as Teams. One clan's name contains a reserved term, so it
|
||||
lands in staff's review queue. Pass 2 then writes forum threads and posts in them, and two open
|
||||
reports. Once report, block and hide exist (`USER_SAFETY.md`), it also writes reports on chat
|
||||
lines.
|
||||
- **Event history and the calendar** (pass 1, core). Two finished runs of the seeded events, with
|
||||
their steps, log, participants (stand-ins, and `demo-player` through its link) and published
|
||||
results, so the history and results pages fill and `demo-player`'s inbox has the results
|
||||
notification. The three seeded events are scheduled in the coming days, so the public calendar
|
||||
shows them.
|
||||
- **Rust player stats** (pass 2, Module-Rust `onDemoSeed`). About ten stand-in players with wipe
|
||||
stats (kills, deaths, NPC kills, playtime, gather totals, weapon kills) on the rig's current
|
||||
wipe, an earlier wipe for the Wipes tab, and a day of feed lines (kills, joins, chat), so the
|
||||
leaderboard, profiles and feed are populated. The online list stays honest at zero.
|
||||
|
||||
### 11.3 Open questions
|
||||
|
||||
1. **Stand-in Steam IDs.** Every 17-digit `7656119…` number can belong to a real person, and these
|
||||
would appear on public leaderboards. *Recommended:* check each candidate against Steam's public
|
||||
profile page before using it, keep only IDs with no profile, and list them in the fixture with
|
||||
that check recorded. `demo-player`'s existing stand-in gets the same check.
|
||||
2. **Invented Discord IDs.** The moderation pages show Discord user ids. *Recommended:* synthetic
|
||||
ids from a block well below any issued since 2016, named in the fixture as demo data. Nothing on
|
||||
the demo links them to Discord.
|
||||
3. **Names.** *Recommended:* invented gamer tags ("Rusty_Pete", "Scrap Baron", …), never a real
|
||||
person's name, and nothing offensive even in the seeded filter hits, which show a placeholder
|
||||
word.
|
||||
|
||||
|
||||
138
website/USER_SAFETY.md
Normal file
138
website/USER_SAFETY.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Report, block and hide — user safety on the website and in the app
|
||||
|
||||
**Status:** plan of record, 2026-10-29. Nothing built yet. Each phase stops for the org lead
|
||||
before the next one starts.
|
||||
|
||||
**Why now.** Google Play's content-rating questionnaire for the Android app (`android/PLAN.md` M8)
|
||||
was answered "users or user-generated content can be reported". The app shows other players' chat
|
||||
(the Rust live feed) and has no way to report it or to stop seeing someone. Play's user-generated
|
||||
content policy expects both wherever that answer is given. The org lead chose to make the answer
|
||||
true rather than retake the questionnaire (S1).
|
||||
|
||||
## 1. What a member can do
|
||||
|
||||
| Action | Who | Where | Effect |
|
||||
|---|---|---|---|
|
||||
| **Report** | any signed-in member | a chat line (web + app), a forum thread, post or upload (web; app later, §6) | a row in the existing **Reports** queue (`TEAMS.md` §5.6). Changes nothing on its own |
|
||||
| **Hide for me** | any signed-in member | the same items | that one item disappears from **their own** view, on every device. No report is filed |
|
||||
| **Block** | any signed-in member | the author of any of those items | everything that author writes (chat lines and forum content) disappears from the blocker's view, on the website and in the app, on every device. Undone from *Account → Blocked* |
|
||||
| **Hide for everyone** | staff (and, for their own forum, Team leaders, as today) | the Reports queue, the forum's existing moderation menu, and the item's own menu | the item disappears for everybody except staff, who see it with a *Hidden by staff* marker and can undo it |
|
||||
|
||||
A report and a hide are separate on purpose, as `TEAMS.md` §5.6 already says: filing a report must
|
||||
never be a way to hide something. *Hide for me* is the member's own tool, and *Hide for everyone* is
|
||||
staff's.
|
||||
|
||||
## 2. Decisions (org lead, 2026-10-29)
|
||||
|
||||
| # | Question | Answer |
|
||||
|---|---|---|
|
||||
| S1 | The questionnaire says chat is reportable, and it is not. Retake it, drop chat from the app, or add report/block? | **Add report, block and hide.** The questionnaire stays as answered |
|
||||
| S2 | Where does a block live? | **On the account, everywhere.** Saved on the site against the blocker's account, so it applies on the website and in the app, on every device |
|
||||
| S3 | "Hide": who hides, and from whom? | **Both.** A member can hide an item for themself, and staff can hide it for everyone |
|
||||
| S4 | The app has no Teams or forum screens. Does the app get the forum? | **Plan it** (§6, `android/PLAN.md` M21). In the app, report/block/hide go on chat lines now |
|
||||
| S5 | When does the Android `edge` → `main` cutover happen? | **After this feature ships**, so the first Play bundle already has it |
|
||||
|
||||
## 3. What exists today
|
||||
|
||||
- **Reports.** `content_reports` (`TEAMS.md` §5.6) is generic by design, but
|
||||
`contentReports.model.js` accepts only `team_forum_thread`, `team_forum_post` and
|
||||
`team_forum_upload`, and finds the owning Team for each. Filing is
|
||||
`POST /player/teams/:slug/forum/report`. The queue is `GET /admin/moderation/reports`, and
|
||||
`POST /admin/moderation/reports/:id/handle` claims, actions or dismisses a report. "Actioned" only
|
||||
records a decision; it does nothing to the content.
|
||||
- **Staff hide in the forum.** `team_forum_moderation` already records `hide`/`unhide` (and pin,
|
||||
lock, delete) by a leader or staff, through `POST …/forum/threads/:id/moderate` and
|
||||
`…/posts/:id/moderate`.
|
||||
- **Chat lines.** Each one is a `rust_events` row in Module-Rust (`kind = 'player.chat'`, with an
|
||||
`id`, `server_id`, `steam_id`, time and raw frame). The website and the app both read them from
|
||||
the public `GET /public/rust/servers/:id/events`. The app polls it signed out or in.
|
||||
- **No blocks or per-member hides** exist anywhere.
|
||||
|
||||
## 4. Design (recommendations; §5 lists what needs an answer)
|
||||
|
||||
### 4.1 Core (`website`)
|
||||
|
||||
- **Module-owned report targets.** A new optional module hook, `reportTargets`, declares the types
|
||||
a module's content can be reported as (for Rust: `rust.chat`). Each type answers two questions for
|
||||
core: `snapshot(id)` (author, text, time, where it was said), and `hide(id, actor)` /
|
||||
`unhide(id, actor)`. Core files the report, stores the snapshot and runs the queue. The module owns
|
||||
the content and its hidden flag. This is a `MODULE_API` minor bump, **1.20.0**. The Integration
|
||||
Kit re-pins and gains a paragraph.
|
||||
- **One filing route for every target**: `POST /player/reports {targetType, targetId, reason,
|
||||
detail?}`. Forum types keep working through the existing forum route, which then calls the same
|
||||
model.
|
||||
- **`content_reports.snapshot JSON`**: what the item said when it was reported. Chat lines are
|
||||
pruned with the rest of `rust_events`, and a queue row must still show what was said after that.
|
||||
Forum reports fill it too, so an author editing a post after the report cannot change what staff
|
||||
read.
|
||||
- **The queue gains *Hide for everyone*** beside actioned and dismissed. It calls the forum's
|
||||
existing hide or the module's `hide`, and records the report as actioned with that action named.
|
||||
- **Blocks**: `user_blocks (blocker_user_id, kind 'user'|'game', blocked_user_id NULL, module_id
|
||||
NULL, external_id NULL, display_name snapshot, created_at)`. A game block names a game identity
|
||||
(for Rust, a Steam ID), because most people in chat have no site account. When that identity is
|
||||
linked to an account, blocking either one hides both.
|
||||
- **Per-member hides**: `user_hidden_items (user_id, target_type, target_id, created_at)`.
|
||||
- **Routes**: `GET/POST/DELETE /auth/me/blocks`, `GET/POST/DELETE /auth/me/hidden`. Their
|
||||
OpenAPI annotations and spec are regenerated as usual.
|
||||
- **Filtering.** Signed-in forum reads drop blocked authors and hidden items on the server. The
|
||||
public chat feed is shared and cached, so it cannot be filtered per reader on the server. The
|
||||
website and the app filter it with the member's lists instead, read once per session and kept
|
||||
current after each block or hide. Module client chunks reach those lists through a new client
|
||||
module-API call.
|
||||
- **Web UI.** The forum's thread and post menus gain *Hide for me* and *Block author*, beside the
|
||||
existing *Report*. A new *Account → Blocked* page lists blocks and undoes them.
|
||||
|
||||
### 4.2 Module-Rust
|
||||
|
||||
- `reportTargets['rust.chat']` with `snapshot` (the speaker's name and Steam ID, the line, server,
|
||||
time, and the linked account if there is one) and `hide`/`unhide`.
|
||||
- **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 live feed's chat lines gain a menu**: *Report*, *Hide for me*, *Block player*, and for
|
||||
staff *Hide for everyone*.
|
||||
- On the demo, all of this stays **open**: it is what a Play reviewer tests, and the wipe resets it.
|
||||
|
||||
### 4.3 Android app (chat lines only, S4)
|
||||
|
||||
- **Long-press a chat line** for the same menu. Report opens a sheet with the six reasons and an
|
||||
optional detail. A member who is signed out gets *Sign in to report or block*.
|
||||
- **Account → Blocked** lists blocks and undoes them, mirroring the web page.
|
||||
- The feed filters with the member's lists, as in §4.1.
|
||||
|
||||
## 5. Questions for the org lead
|
||||
|
||||
Each has a recommendation; nothing is built until they are answered.
|
||||
|
||||
1. **Signed out.** Can a signed-out visitor report a chat line? *Recommended: no.* Reporting, hiding
|
||||
and blocking all need an account, as forum reports do today. Otherwise the queue is open to
|
||||
anyone with `curl`. Play needs the mechanism to exist, not to be anonymous.
|
||||
2. **Blocking someone who has no account.** Can a member block a Steam ID that is linked to nobody?
|
||||
*Recommended: yes* (§4.1's game block). Almost everyone in a Rust chat feed is unlinked.
|
||||
3. **Blocking staff.** Can a member block a moderator or admin? *Recommended: yes.* A block only
|
||||
hides content from the blocker. It changes nothing about staff powers, and it doesn't stop staff
|
||||
seeing the blocker.
|
||||
4. **What a block does to the blocked person.** Nothing beyond hiding, *recommended*. There are no
|
||||
direct messages to stop, and telling someone they were blocked invites the harassment the block
|
||||
was for.
|
||||
5. **Staff view of hidden chat.** Do staff still see a line hidden for everyone? *Recommended: yes,
|
||||
marked*, so a hide can be checked and undone, as the forum already treats its own hidden posts.
|
||||
|
||||
## 6. The team forum in the app (planned, not scheduled)
|
||||
|
||||
The app has no Teams screens. `android/PLAN.md` M21 plans them: the member's Teams, a Team's page,
|
||||
the forum's threads and posts (read, reply, new thread), the same report/hide/block menu on posts
|
||||
and threads, and the forum's push stream. When it ships, the app lets members post content, so the
|
||||
Play declarations gain "user-generated content: collected". M21 says so.
|
||||
|
||||
## 7. Build order
|
||||
|
||||
1. **This plan** (docs). Answers to §5 recorded here.
|
||||
2. **Core** (`website`): §4.1, `MODULE_API` 1.20.0, tests, OpenAPI, `BACKEND_DESIGN.md`,
|
||||
`MODULE_API.md`, `TEAMS.md` §5.6 amended. One PR.
|
||||
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.
|
||||
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
|
||||
blocked.
|
||||
6. Then `android/PLAN.md` M8's cutover (S5).
|
||||
Reference in New Issue
Block a user