---
path: /studio/aja/content-moderation
surface: studio
domain: aja
auth: signed-in + studio entitlement
source: apps/oshun/web/src/app/studio/aja/content-moderation/page.tsx
status: walked
last_walked:
  '2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data
  (2xx), console/page-errors, expected content, screenshot verified; live
  screen-reader, touch, offline, and telemetry-delivery checks pending a manual
  AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; body
  re-derived 2026-06-03 from current source (lane-console architecture)'
---

# Aja Content Moderation

## Purpose

Admin lane console for the moderation ban lifecycle, wired to the real
`@aja/content-moderation` `BanManager` via `/v1/admin/aja/content-moderation`:
issue user bans (with repeat-offense escalation tracked through
`previousViolations`), check scope-aware ban status, and revoke. The library's
ML upload detectors are deliberately not surfaced here.

## Entry points

- **Direct URL** — `/studio/aja/content-moderation` (signed-in + studio entitlement)
- **Breadcrumb** — Studio / Aja / Content Moderation
- **Sibling quick-actions from this page** — Content Security, Consent
  Management, Privacy Protection, Data Retention, Back to Studio

## Layout regions

`page.tsx` mounts `ShellLayout` with `active="studio"` and renders four panels.

- **Breadcrumb panel** (`data-aja-content-moderation-breadcrumbs`):
  `nav aria-label="Aja Content Moderation breadcrumbs"` — Studio (link) / Aja /
  Content Moderation (`aria-current="page"`)
- **Workspace panel** (`data-aja-content-moderation-workspace`):
  - `<h1>` "Aja Content Moderation Workspace" (via `WorkspaceHeading`)
  - `<p data-aja-content-moderation-summary>` — "Issue, check, and revoke user
    bans with repeat-offense escalation and scope-aware enforcement — the
    moderation ban-lifecycle control plane."
  - The ban form + check form lanes (rendered once the catalog loads)
- **Route Map panel** (`data-aja-content-moderation-route-map`): `<h2>` "Route
  Map" + one `<article>` per `STUDIO_AJA_CONTENT_MODERATION_ROUTE_MAP` entry
  (6 entries)
- **Quick actions panel**: `.quickAction` `<Link>`s — see Interactions

## States

- [ ] **Loading** — `data-aja-cm-loading` "Loading moderation catalog…"
- [ ] **Unauthorized** — on 401/403, `data-aja-cm-unauthorized` ("Access
      restricted." + reason, default "Aja workspace scope required to view
      content moderation.")
- [ ] **Error** — non-OK / malformed / unreachable → `data-aja-cm-error`
- [ ] **Ready (forms)** — catalog present: ban form + check form
- [ ] **Ban result** — `data-aja-cm-ban-result` with `data-aja-cm-ban-id`,
      `data-aja-cm-ban-violations` (prior violations), active/type
- [ ] **Check result** — `data-aja-cm-check-result` with
      `data-aja-cm-check-banned` and `data-aja-cm-check-history`
- [ ] **Action error** — `data-aja-cm-action-error` (e.g. "User, reason,
      issued-by, and at least one category are required." / "A user ID is
      required to check status.")

## Interactions

### Ban form (`data-aja-cm-ban-form`)

- [ ] **User ID** — `input data-aja-cm-field-user`
- [ ] **Ban type** — `select data-aja-cm-field-type` (options from
      `catalog.banTypes`)
- [ ] **Categories (comma-separated)** — `input data-aja-cm-field-categories`
- [ ] **Reason** — `input data-aja-cm-field-reason`
- [ ] **Issued by** — `input data-aja-cm-field-issuedby`
- [ ] **Issue ban** — `button data-aja-cm-ban-submit`; `POST
      /v1/admin/aja/content-moderation/ban` with `{ userId, type, reason,
      categories, issuedBy }`

### Check form (`data-aja-cm-check-form`)

- [ ] **Check user ID** — `input data-aja-cm-field-check`
- [ ] **Check status** — `button data-aja-cm-check-submit`; `POST
      /v1/admin/aja/content-moderation/check` with `{ userId }`

### Route Map panel (`data-aja-content-moderation-route-map`)

Non-interactive `<article>` blocks from `STUDIO_AJA_CONTENT_MODERATION_ROUTE_MAP`
(6 entries): `/studio/aja/content-moderation` plus `…/policies/[policyId]`,
`…/reviews/[reviewId]`, `…/cases/[caseId]`, `…/parity/[parityId]`,
`…/governance/[governanceId]`.

### Quick actions panel

- [ ] **Open Aja Content Security workspace** → `/studio/aja/content-security`
- [ ] **Open Aja Consent Management workspace** → `/studio/aja/consent-management`
- [ ] **Open Aja Privacy Protection workspace** → `/studio/aja/privacy-protection`
- [ ] **Open Aja Data Retention workspace** → `/studio/aja/data-retention`
- [ ] **Back to Studio workspace index** → `/studio`

## Data & contracts

- **Reads**: `GET /v1/admin/aja/content-moderation` (catalog: `banTypes`,
  `categories`) on mount via `buildBffAuthHeaders()`, `cache: 'no-store'`
- **Writes**: `POST …/ban` → `{ ban }`; `POST …/check` → `{ status }`
- **Realtime**: none
- **Caching**: client fetch on mount (no-store); SSR shell only
- **Auth**: BFF endpoints admin-scoped + fail-closed (401/403 → unauthorized
  render); the route is signed-in + studio (proxy gate)

## Cross-references

- Parent: [`../../studio-overview.md`](../../studio-overview.md)
- Component sources:
  - `apps/oshun/web/src/components/studio/StudioAjaContentModerationWorkspace.tsx`
  - `apps/oshun/web/src/components/studio/StudioAjaContentModerationRouteMap.ts`
- Siblings from quick-actions: `/studio/aja/content-security`,
  `/studio/aja/consent-management`, `/studio/aja/privacy-protection`,
  `/studio/aja/data-retention`

## Open questions / known gaps

- [ ] Route-map sub-routes (`policies`/`reviews`/`cases`/`parity`/`governance`)
      are descriptive only; confirm implementation status
- [ ] Document the exact studio entitlement / proxy gate governing `/studio/aja/*`
