Skip to content

Contested Likeness Review

Contested Likeness Review

This flow lets a user submit an NSFW onboarding/reference image for manual verification when the only automatic validation failure is likeness matching against the trusted reference/KYC image.

Sirloin stores review state per uploaded reference dataset image in characters.reference_dataset_images.status. The durable states are CHARACTER_DATASET_IMAGE_STATUS_REVIEW, CHARACTER_DATASET_IMAGE_STATUS_SELECTED, CHARACTER_DATASET_IMAGE_STATUS_SUPERSEDED, and CHARACTER_DATASET_IMAGE_STATUS_REJECTED.

Architecture

sequenceDiagram
participant Brisket
participant Sirloin
participant Brain
participant Strip
participant Audit as strip_audit_logs
Brisket->>Sirloin: VerifyCharacterReferenceDatasetImage
Sirloin->>Brain: ScoreImageForKitsune
Brain-->>Sirloin: FACE_NOT_MATCHING_REFERENCE only
Sirloin-->>Brisket: manual_review_eligible = true
Brisket->>Sirloin: RequestCharacterLikenessManualReview(images[])
Sirloin->>Sirloin: Insert/update reference_dataset_images(status=REVIEW)
Strip->>Sirloin: StripListLikenessReviews
Sirloin-->>Strip: pending review rows + evidence URLs
Strip->>Sirloin: StripResolveLikenessReview(approved/rejected)
alt approved
Sirloin->>Sirloin: Promote image to SELECTED and supersede older selected row for the slot
else rejected
Sirloin->>Sirloin: Mark image REJECTED
end
Sirloin->>Audit: Record admin action

Behavior

  • Brisket shows one batch action to submit all currently eligible likeness failures for manual verification.
  • Brisket reloads unfinished onboarding by preferring a REVIEW image for each upload slot, falling back to SELECTED when no review image exists.
  • SELECTED means the image is usable for the character dataset. REVIEW means it is pending operations approval and must not be treated as passed validation.
  • Strip exposes a permissioned likeness review queue and approve/reject actions for operations users. Rejections carry a structured reason and optional note; resolved decisions are browsable in Likeness Review History, and reasons are surfaced to the end user per Likeness Review User Outcomes.
  • The pending queue paginates by character, not by image row: a page always carries every pending image for the characters on it, so a submission’s card (and its bulk actions) can never be split across pages.
  • Sirloin records admin decisions through the existing strip_audit_logs table.

Custom-VI proof of creation

A custom-VI (18+ AI-generated) character can be rejected with STRIP_LIKENESS_REJECTION_REASON_NEEDS_PROOF_OF_CREATION when the reviewer cannot confirm the user created it. This reason is actionable rather than terminal:

  • The character’s card CTA becomes “Add proof of creation”, and onboarding resumes directly on the verification-evidence step (rather than the photo-upload step), so the proof form the user needs is reachable instead of skipped.
  • Resubmitting the evidence re-opens the review: the same source photos return to the queue with the updated proof. Photos are never re-uploaded or deleted — only their review status changes.
  • Brisket detects this state from the per-image rejection_reason exposed on CharacterDatasetImage, using the latest row per slot, so once the resubmission lands the character behaves like a normal pending review again.
  • Other rejection reasons (real images of someone else / unusable / other) remain terminal — they do not reopen the evidence step.

Persistence Notes

characters.reference_dataset_images tracks upload versions. New manual-review submissions create or refresh rows with status = CHARACTER_DATASET_IMAGE_STATUS_REVIEW, upload_path, and version_path. The latest rejection reason and note are stored on the row (rejection_reason, rejection_note — migration 122); the resolution moment is stamped on the row (reviewed_at, migration 123) because it drives outcome-email batching, while resolver identity stays in strip_audit_logs.

Resubmitting custom-VI proof of creation (UpdateCharacterCustomViVerification) re-opens review by flipping the character’s NEEDS_PROOF_OF_CREATION-rejected rows from REJECTED back to REVIEW and clearing their rejection_reason/rejection_note. Rows rejected for other reasons are left untouched.

Outcome Emails

When the last pending review for a character is resolved, sirloin sends one outcome email covering exactly the slots reviewed since the previous outcome email (characters.manual_review_outcome_email_sent_at, advanced only after a successful send). The email lists each reviewed slot with its verdict. Character-level reasons (needs proof of creation, real images of someone else) are stated once above the slot list with CS-authored copy; per-photo reasons appear under the slot row, and OTHER rejections surface the reviewer’s note to the user (trimmed and HTML-escaped) — notes on standard reasons are never shown.