feat: add automated community moderation
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# Community content moderation
|
||||
|
||||
SHD.build applies one moderation lifecycle to public comments, profile
|
||||
biographies, custom avatars, guides, and build descriptions. The shared server
|
||||
service lives in `src/lib/server/community-moderation.ts`. Content save actions
|
||||
must use that service before publication.
|
||||
|
||||
## Publication decisions
|
||||
|
||||
The moderation service combines local rules with the configured provider
|
||||
result:
|
||||
|
||||
- Clean content publishes without human review when automatic publication is
|
||||
enabled.
|
||||
- Banned terms, PII checks, link limits, provider flags, and configured score
|
||||
thresholds send content to Community Oversight.
|
||||
- Provider failures send content to Oversight when fail-closed behavior is
|
||||
enabled.
|
||||
- An allow term can suppress the matching local term rule. It cannot override a
|
||||
provider flag or threshold.
|
||||
- A user report sends visible content to Oversight. One report does not hide
|
||||
the content because reports can be mistaken or abusive.
|
||||
|
||||
The service records every automated scan in `content_moderation_scans`.
|
||||
Automated scans do not write to the human ISAC audit log. Human policy changes
|
||||
and review decisions use the shared audit utility.
|
||||
|
||||
## Content storage
|
||||
|
||||
Comments use an explicit status:
|
||||
|
||||
- `visible`: public and approved
|
||||
- `pending_review`: hidden while Oversight decides
|
||||
- `removed`: unavailable after author removal or an Oversight decision
|
||||
|
||||
Long-form content uses immutable pending revisions. Profile and guide edits that
|
||||
need review go into `moderated_content_revisions`; the public row keeps its last
|
||||
approved text. Approval copies the reviewed revision into the public row.
|
||||
Rejection preserves the revision and review evidence without publishing it.
|
||||
|
||||
Custom avatar uploads remain active after a clean image scan. A flagged image
|
||||
or fail-closed scan error creates an `avatar_reviews` item and is unavailable
|
||||
through the public avatar route until approval. An Agent report also creates a
|
||||
review item, but leaves an already-published avatar visible while Oversight
|
||||
decides.
|
||||
|
||||
## ISAC responsibilities
|
||||
|
||||
ISAC Site Controls stores the site-wide policy:
|
||||
|
||||
- provider scanning and per-content enablement;
|
||||
- clean-content and provider-failure behavior;
|
||||
- provider model;
|
||||
- PII, link, and duplicate checks;
|
||||
- category review and priority thresholds;
|
||||
- allowed and banned local terms.
|
||||
|
||||
Site Controls requires `site.settings.manage`. Community Oversight handles text
|
||||
decisions through the existing comment and profile permissions. Avatar
|
||||
Oversight handles custom images through `avatars.review`.
|
||||
|
||||
Moderators must record a reason when they approve, reject, remove, restore,
|
||||
dismiss, or resolve a queued item. The action must append an audit event after
|
||||
the content and review records change.
|
||||
|
||||
## User reports
|
||||
|
||||
Authenticated Agents can report visible content they do not own. The report
|
||||
control must identify the content type and stable content ID, validate that the
|
||||
content remains public, and create a pending `user_content_reports` row.
|
||||
Duplicate pending reports from the same Agent for the same content are rejected.
|
||||
|
||||
A reviewer either dismisses the report and keeps the content or resolves the
|
||||
report through a moderation action. Report details must not copy authentication
|
||||
secrets or unnecessary personal information into the audit log.
|
||||
|
||||
## Adding a public UGC field
|
||||
|
||||
Guide authoring, build descriptions, and later public text fields must complete
|
||||
this checklist:
|
||||
|
||||
1. Normalize and validate the submitted text before scanning.
|
||||
2. Call `moderateCommunityText` with a supported content type.
|
||||
3. Place the shared `ModerationDisclosure` beside the submission control.
|
||||
4. Publish only an `approved` result.
|
||||
5. Store held long-form text as an immutable revision so the last approved
|
||||
version remains public.
|
||||
6. Call `recordCommunityModerationScan` with the stable content ID, author, hash,
|
||||
policy version, provider evidence, and local-rule matches.
|
||||
7. Add the content type to the Community Oversight queue and permission checks.
|
||||
8. Add the shared report control to the public rendering.
|
||||
9. Delay follower, owner, or reply notifications until the content becomes
|
||||
public.
|
||||
10. Add approval, rejection, report dismissal, author notification, and audit
|
||||
tests.
|
||||
11. Confirm every public query selects approved content explicitly.
|
||||
|
||||
Do not add a route-specific word filter, provider client, threshold table, or
|
||||
moderation status vocabulary. Extend the shared service and schema when a new
|
||||
content type needs more evidence or a different review presentation.
|
||||
|
||||
## Operations and calibration
|
||||
|
||||
The API key stays in `OPENAI_MODERATION_API_KEY`. The server can use
|
||||
`OPENAI_MECHANIC_API_KEY` as a temporary fallback, but production should use a
|
||||
separate moderation credential.
|
||||
|
||||
Public submission notices link to the Terms & Privacy page instead of
|
||||
summarizing provider data handling beside each form. The Terms & Privacy page
|
||||
must not promise that OpenAI never retains or uses moderation inputs. It states
|
||||
that OpenAI controls downstream processing under its then-current terms, data
|
||||
controls, and account settings. If SHD.build enables an OpenAI data-sharing
|
||||
option, update the legal copy before sending user content under that setting.
|
||||
|
||||
Threshold changes increment the policy version. Stored scans retain their model
|
||||
and policy version so reviewers can interpret older decisions. Test threshold
|
||||
changes against representative Division terminology before activation,
|
||||
including weapon names, damage descriptions, kills, status effects, and build
|
||||
strategy language.
|
||||
|
||||
Database migrations that add moderation states or revision storage follow the
|
||||
repository database-preservation procedure: stop this project's development
|
||||
server, verify the database path, create and verify a JSON backup, migrate,
|
||||
check foreign keys and critical record counts, then restart and health-check.
|
||||
@@ -468,6 +468,27 @@ Public biographies and community comments use the shared `MarkdownEditor` and
|
||||
remaining-character count, concise help, a readable editorial typeface, and a
|
||||
minimum 44px touch target for toolbar actions on narrow screens. It supports
|
||||
headings, lists, quotes, links, emphasis, and inline code. Raw HTML is escaped.
|
||||
|
||||
Public user-generated content uses the shared `ContentReport` control when the
|
||||
viewer is authenticated and does not own the content. Keep the control visually
|
||||
secondary to reading and reply actions. Its disclosure names the concern,
|
||||
accepts optional bounded context, and states that a report requests human review
|
||||
without removing content immediately. On narrow screens, present the report
|
||||
form as a fixed bottom panel with full-width touch controls.
|
||||
|
||||
Submission forms for public user-generated text and custom images use the
|
||||
shared `ModerationDisclosure` notice. Place it after the input and before
|
||||
validation or submission controls. The always-visible notice states that
|
||||
automated safety review may occur and links to the Terms & Privacy section for
|
||||
the details. Do not hide this disclosure in a hover-only tooltip.
|
||||
|
||||
Automated moderation is exception-based. Clean content publishes without an
|
||||
Oversight task when policy permits. Flagged comments remain hidden with an
|
||||
explicit pending state; flagged long-form edits leave the last approved version
|
||||
public. Flagged custom avatars use the default avatar publicly until approval.
|
||||
Community Oversight shows automated evidence and Agent reports in one pending
|
||||
queue, but labels their sources separately. Human approval, rejection, removal,
|
||||
and report dismissal require a reason and confirmation.
|
||||
Only HTTP and HTTPS links are active; rendered links open separately with UGC,
|
||||
nofollow, and opener-isolation attributes. `MarkdownContent` must receive HTML
|
||||
from the server renderer, never raw user input or client-rendered Markdown.
|
||||
|
||||
@@ -82,6 +82,24 @@ explains registration closure. This guard runs on the server, so a crafted form
|
||||
submission or OAuth callback cannot create an account while registration is
|
||||
closed.
|
||||
|
||||
## Community content moderation
|
||||
|
||||
Comments, profile biographies, custom avatars, guides, and build descriptions
|
||||
share the moderation lifecycle documented in
|
||||
[`CONTENT_MODERATION.md`](CONTENT_MODERATION.md). Local rules and the configured
|
||||
provider classify each submission before publication. Clean submissions can
|
||||
publish without human work. Flagged submissions and fail-closed provider errors
|
||||
enter the appropriate Oversight queue.
|
||||
|
||||
Agents may report visible content they do not own. Reports enter Oversight
|
||||
without hiding the content. Human decisions require a reason and append an
|
||||
audit event. Long-form edits that need review use immutable pending revisions
|
||||
so the last approved biography, guide, or description remains public.
|
||||
|
||||
ISAC Site Controls owns provider behavior, content scopes, thresholds, link and
|
||||
PII checks, and allowed or banned terms. API credentials remain in server
|
||||
environment configuration.
|
||||
|
||||
## Increment plan
|
||||
|
||||
1. Product shell and anonymous local-draft builder.
|
||||
|
||||
Reference in New Issue
Block a user