# Operating the moderation queue Administrator-only. This is the operational manual for the queue at `/admin/moderation`, and the evidence base for a compliance-improvement plan if a store ever asks for one. ## Daily loop 1. Open `/admin/moderation`. The default tab is **Open**, sorted oldest first. 2. Read the SLA badge in the header. It shows the age of the oldest unresolved report against `moderation_sla_hours` (currently {{ moderation_sla_hours() }}). Green means every open report is inside the window; red means at least one is over, and the count of breaches is shown. 3. Work the oldest first. Anything at `critical` severity jumps the queue: those are the sexual, exploitative, self-harm and illegal categories, plus filter failures. 4. Open a report. It renders the reported item in place, the author, the number of distinct reports on that target, and every prior decision against that author. 5. Decide. Every decision writes a `moderation_actions` row, an audit event, and a notification to the affected user stating what was decided and why. ## Severity ladder | Severity | Assigned to | Handling | |---|---|---| | `info` | Spam | Batch it; low harm | | `warn` | Everything else by default | Normal queue order | | `critical` | Sexual, exploitative, self-harm, illegal, and any filter failure | Immediately, ahead of the queue | ## The state machine `open` -> `acknowledged` -> `actioned` or `dismissed`. **Escalate** raises severity to `critical` and returns the report to `acknowledged` for a second opinion rather than resolving it. Resolution is a single atomic conditional update: if two administrators decide the same report simultaneously, exactly one succeeds and the other is answered 409. Never work around that by re-opening and re-deciding - re-read the report first. ## Choosing a sanction | Situation | Sanction | |---|---| | First offence, low harm, plausibly accidental | Remove content, reason stated | | First offence, deliberate | Remove content plus a warning | | Repeat offence, or harassment of a person | Suspend, 24 to 168 hours | | Sexual content involving a minor, credible threat, or coordinated abuse | Ban immediately, then preserve evidence | | Report is not a violation | Dismiss - the reporter is told, and that is not a penalty for reporting | State the reason in the field that is shown to the user. A decision with no reason is a decision the user cannot learn from and cannot dispute. ## What you cannot do You cannot action an administrator who registered before you. The attempt is refused and audited with `result="denied"`. This is enforced server-side, so it also binds the assistant. You cannot remove a direct message, an account, a workspace, a poll or assistant output as *content* - those have no removal path. Act on the account instead. ## Filter tuning `moderation_filter_mode` at `/admin/settings` selects the platform posture: | Mode | Effect | |---|---| | `off` | Classification does not run | | `label` | Classification only labels maturity, never reports | | `review` | Default. A match publishes and raises a system report | | `block` | A match is refused at creation | Move to `block` only with evidence from the queue. On a developer platform a blanket block produces false positives on security research and error messages, which is why `review` is the default. Watch the volume of `origin=filter` reports for a week before changing it. ## Evidence for a compliance plan Everything a reviewer or regulator can ask for is already queryable: - **Throughput and latency**: `content_reports.created_at` versus `resolved_at`. - **Backlog and SLA**: the queue's own SLA snapshot, and the count of open reports older than the window. - **Decisions and their reasons**: `moderation_actions`, one row per decision, linked to its report. - **Statements of reasons**: the moderation notification sent to each affected user. - **Immutable trail**: the `moderation` category in the audit log at `/admin/audit-log`. ## App review checklist The operator transcribes these into the store listing; every answer is a fact about the running platform, not a promise. **Review account.** Provision a normal member account for the reviewer and hand over its credentials in the review notes. Then seed it so both safety controls can actually be exercised: it must see content authored by **someone else** (so the Report and Block controls are present - both hide on your own content), and it must not be an administrator (so the moderation queue is correctly inaccessible). Check `registration_open` is `1` before submitting, or a reviewer who loses the account cannot make another one. **Review notes.** State, in this order: where the report control is (every content action bar); where blocking is (the same bar, and the profile); that reports are answered within `moderation_sla_hours`; that the terms and community guidelines are linked from the footer of every page and from the signup form; that account deletion is self-service at `/profile/{username}/delete`; and that **all user code executes on DevPlace servers in containers, never on the device** - the client only ever renders results, and nothing downloads or alters executable code. **Age-rating questionnaire.** The four answers that matter are all yes, and each has an artifact: moderation systems (`/admin/moderation`), content filtering (the classifier at the five choke points), reporting tools (`POST /reports/{target_type}/{target_uid}`), blocking (`/block/{username}`). The minimum age is `moderation_minimum_age`; user-generated content is unrestricted-web-access-free (there is no in-app browser) but does include user-published workspaces reachable at `/p/`, indexed at `/workspaces/index`. **Privacy declarations.** Declare the categories the [Privacy Policy](/docs/privacy.html) lists - contact info, user content, identifiers, usage data, diagnostics - as **linked to the user** and **not used for tracking**. There is no third-party analytics SDK, no advertising, and no cross-app or cross-site tracking anywhere in the codebase, so no tracking declaration applies. The one third-party processor of user content is the AI provider named in `ai_third_party_provider`, and it receives nothing without a granted `ai_third_party` consent. **Keep them untriggered.** Adding social login, an in-app purchase path, purchasable randomness, advertising, or cross-app tracking each creates a new mandatory obligation. If one is added, its obligation ships in the same change. ## Reversal A wrong removal is undone with the **Restore content** decision, or from `/admin/trash`, which restores the whole deletion event under one stamp. Nothing a moderator removes is destroyed until it is purged.