Add the trust and safety subsystem and the App Store compliance work

Implements the moderation and consent obligations a social platform carries,
so the web version and any client that speaks to it enforce the same rules.

Moderation core (services/moderation/, database/moderation.py): a reportable
target registry, the content filter and its choke points, the report queue with
atomic resolution, enforcement actions, consent tracking, maturity gating, and
account deletion with a grace window.

Surfaces: POST /reports plus the member report list, /admin/moderation and the
per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and
account deletion under /profile, the report button and dialog partials, the
maturity gate, and the moderation stylesheet and ReportDialog client.

Every user-generated surface stays reportable by construction: new content tables
are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a
reason, and the registry test fails the suite on anything left unclassified.

Docs: community guidelines, content moderation, intellectual property, privacy,
terms, contact, and the admin-only moderation operations page, plus the
moderation API group and the Devii moderation actions.

Compliance record: applecomp.md is the requirement register, applechanges.md the
gap analysis against this codebase, and appleimpl.md the implementation design
they resolve to.

Tests cover the report flow, admin moderation, consent, account deletion, terms
acceptance, workspaces, and the registry invariant across the unit, api, and e2e
tiers.
This commit is contained in:
2026-08-09 00:18:20 +02:00
parent 68c2bbe387
commit 8e9d3fad98
348 changed files with 10633 additions and 238 deletions
+136
View File
@@ -0,0 +1,136 @@
# retoor <retoor@molodetz.nl>
from __future__ import annotations
from typing import Any, Optional
from devplacepy.schemas.admin import AdminUserOut
from devplacepy.schemas.base import _Out
class ReportOut(_Out):
uid: Optional[str] = None
target_type: Optional[str] = None
target_uid: Optional[str] = None
target_url: Optional[str] = None
reason: Optional[str] = None
reason_label: Optional[str] = None
detail: Optional[str] = None
severity: Optional[str] = None
status: Optional[str] = None
origin: Optional[str] = None
categories: list[str] = []
created_at: Optional[str] = None
updated_at: Optional[str] = None
resolved_at: Optional[str] = None
reporter_name: Optional[str] = None
owner_name: Optional[str] = None
report_count: Optional[int] = None
class ReportCreatedOut(_Out):
report: Optional[ReportOut] = None
sla_hours: Optional[int] = None
message: Optional[str] = None
class ReportListOut(_Out):
reports: list[ReportOut] = []
pagination: Optional[Any] = None
status: Optional[str] = None
reasons: list[Any] = []
class ReportReasonsOut(_Out):
reasons: list[Any] = []
severities: list[str] = []
class ModerationActionOut(_Out):
uid: Optional[str] = None
report_uid: Optional[str] = None
action: Optional[str] = None
actor_name: Optional[str] = None
reason: Optional[str] = None
notes: Optional[str] = None
expires_at: Optional[str] = None
created_at: Optional[str] = None
class SlaOut(_Out):
sla_hours: Optional[int] = None
oldest_open_hours: Optional[float] = None
oldest_open_uid: Optional[str] = None
breached: Optional[int] = None
within_sla: Optional[bool] = None
class AdminModerationOut(_Out):
reports: list[ReportOut] = []
pagination: Optional[Any] = None
status: Optional[str] = None
statuses: list[Any] = []
counts: dict = {}
sla: Optional[SlaOut] = None
admin_section: Optional[str] = None
class AdminReportOut(_Out):
report: Optional[ReportOut] = None
actions: list[ModerationActionOut] = []
history: list[ModerationActionOut] = []
available_actions: list[str] = []
can_remove: Optional[bool] = None
subject: Optional[AdminUserOut] = None
sla: Optional[SlaOut] = None
admin_section: Optional[str] = None
class MaturityOut(_Out):
target_type: Optional[str] = None
target_uid: Optional[str] = None
level: Optional[str] = None
source: Optional[str] = None
class ConsentOut(_Out):
kind: Optional[str] = None
label: Optional[str] = None
state: Optional[str] = None
version: Optional[str] = None
granted_at: Optional[str] = None
withdrawn_at: Optional[str] = None
class ConsentListOut(_Out):
consents: list[ConsentOut] = []
class AcceptTermsOut(_Out):
terms_version: Optional[str] = None
accepted_version: Optional[str] = None
class AccountDeletionOut(_Out):
username: Optional[str] = None
grace_hours: Optional[int] = None
retained: list[str] = []
removed: list[str] = []
class WorkspaceIndexItemOut(_Out):
uid: Optional[str] = None
name: Optional[str] = None
slug: Optional[str] = None
owner_uid: Optional[str] = None
url: Optional[str] = None
description: Optional[str] = None
owner: Optional[str] = None
maturity: Optional[str] = None
project_url: Optional[str] = None
class WorkspaceIndexOut(_Out):
workspaces: list[WorkspaceIndexItemOut] = []
pagination: Optional[Any] = None
total: Optional[int] = None