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 actionBehavior
- Brisket shows one batch action to submit all currently eligible likeness failures for manual verification.
- Brisket reloads unfinished onboarding by preferring a
REVIEWimage for each upload slot, falling back toSELECTEDwhen no review image exists. SELECTEDmeans the image is usable for the character dataset.REVIEWmeans 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_logstable.
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_reasonexposed onCharacterDatasetImage, 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.