Skip to content

Likeness Review History

Likeness Review History

This flow extends Contested Likeness Review with CS-facing visibility: a read-only history of every resolved likeness decision, and structured rejection reasons captured at resolve time.

The Strip likeness page has two tabs: Pending (the actionable queue documented in the contested review flow) and Resolved / History (this flow). Both are served by apps/strip/internal/app/handlers/likeness_reviews.go; the history tab is /likeness-reviews?tab=resolved.

Architecture

sequenceDiagram
participant Strip as Strip (Resolved / History tab)
participant Sirloin
participant Images as characters.reference_dataset_images
participant Audit as strip_audit_logs
Strip->>Sirloin: StripListLikenessHistory(search, cursor, limit)
Sirloin->>Images: Page characters with resolved likeness images
Sirloin->>Audit: Load events for those images (entity_type = REFERENCE_DATASET_IMAGE)
Sirloin-->>Strip: characters -> slots -> chronological events
Note over Strip: Read-only. Approve/reject only happens on the Pending tab.
Strip->>Sirloin: StripResolveLikenessReview(rejected, rejection_reason, note)
Sirloin->>Images: status = REJECTED, rejection_reason, rejection_note
Sirloin->>Audit: Record admin decision with reason metadata

Behavior

  • The history tab groups resolved images by character, then by upload slot (face_frontal, full_body, full_body_any, *_nsfw variants). Slots render in the upload-form order — face first — regardless of upload recency, labelled as in brisket: “Face & full chest area”, “Full body front”, “Full body”. Each slot shows its current status (SELECTED / REJECTED / SUPERSEDED) and a chronological timeline of events (UPLOADED, APPROVED, REJECTED, SUPERSEDED) with reviewer email and timestamp.
  • Rejecting an image in the Pending tab now requires a StripLikenessRejectionReason: needs proof of creation, real images of someone else, unusable for generation, or other (with a free-text note). Bulk reject collects a reason per image.
  • Re-uploading a replacement into a slot soft-deletes the previous image row, but its decision trail stays visible in history — the listing ignores reference_dataset_images.deleted_at on purpose. Only deleting the character removes its history from the tab.
  • Pending-tab groups whose character already has resolved decisions (StripLikenessReviewItem.has_history) render a “History” button deep-linking to the history tab pre-filtered to that character.
  • Search matches character name, character ID, user ID, or user email. Results are cursor-paginated per character.
  • The history view performs no mutations; it exists so CS can answer “what happened to this upload and why” without engineering help.

Persistence Notes

  • Migration 122_reference_dataset_image_rejection_reason.sql adds rejection_reason and rejection_note columns to characters.reference_dataset_images, so the latest decision metadata lives on the image row itself.
  • The per-image timeline is reconstructed from strip_audit_logs (entity_type = REFERENCE_DATASET_IMAGE, ordered by created_at). Reviewer identity and historical reasons come from the audit rows, not the image table, so older decisions keep their context even after a slot is re-uploaded.