• T32: moderateFaceBlacklist(imagePath, assetId) — same pluggable seam
    shape as T26, for mode 3's uploaded face photos (需求文档 5 节: filtering
    politically-sensitive likenesses). Default-permissive (auto_pass) when
    unconfigured, per explicit user confirmation this session — deliberately
    not assumed, since a wrong-default here (a real face silently passed
    through) has a different risk profile than T26's garment photos.
    
    T33: forwardToFalAssets (uploads.js) + fal's queue API (falSubmit/
    falPollStatus/falFetchResult) wired into submitGeneration/
    generateDanceVideo, which now dispatches on job.backend between the
    existing H3 Console path (renamed generateDanceVideoViaH3, unchanged
    behavior) and the new generateDanceVideoViaFal. A paid request (userId
    set) routes to fal once FAL_API_KEY is configured; everything else —
    free mode-一, or paid but no key yet — stays on H3 Console exactly as
    before (verified: full suite green, no FAL_API_KEY set in any existing
    test's env).
    
    Endpoints/params (storage/upload/initiate, queue.fal.run/{model}/
    requests/{id}/status vs /requests/{id} as two separate calls,
    Authorization: Key <FAL_API_KEY>) are taken from fal's published docs
    and the fal-js client's own source, not guessed — fal-generation.test.js
    proves this codebase's side of the integration against a mock fal
    server. What remains genuinely unverified (no FAL_API_KEY exists) is
    whether the H3-Console-tuned prompt is interpreted the same way by
    fal's minimax/h3/reference-to-video model, and fal's exact
    terminal-failure status string(s) — flagged in code comments rather
    than guessed at.
    
    Also fixes a real gap T27 shipped with: submitGeneration never received
    userId, so every mode-2 clip's requestedByUserId (M.7) was silently
    null. T27's call site now passes userId (priority stays "normal" —
    only mode 3 skips voting), which also makes mode 2 fal-eligible.
    dance-mode2.test.js's happy-path test now asserts this.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo
    yibo authored
     
    Browse File »
  • Requires login + emailVerified (M.10). Order matters and each step
    undoes the previous one on failure: moderate (T26) before anything
    else exists, so a rejected image leaves nothing behind; create the
    garments row (source: user_paid) before spending, so the ledger entry
    can carry relatedAssetId — a failed spend deletes it back out;
    generation submission failure refunds and deletes it too. Reuses T8/
    T9's submitGeneration whole, same M.7 dedupe cache, then rides the
    same danceQueue/ensureDanceSchedule machinery /api/dance/pick (mode 1)
    already uses — a paid entry only differs by carrying userId/
    amountPaid, which mode 1's free entries don't set.
    
    Async generation failure (job fails after this handler already
    responded) is refunded from ensureDanceSchedule's existing "generating
    -> failed" tick, gated on a findByRelatedAssetAndReason lookup so a
    crash between that write and saveDanceState() persisting it can't
    double-refund — a duplicate here is real money, not a cosmetic bug.
    
    dance-mode2.test.js covers the full chain against a mock H3 Console:
    login/verification/balance gates, a non-image upload rejected before
    anything is spent, the happy path (spend -> auto-tag -> generate ->
    lands in dance schedule, garments row has user_paid/ownerUserId/
    appearanceDescriptor), and — via a second server pointed at a mock H3
    that always fails submission — the async refund path, redelivered
    twice to prove it's exactly-once.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo
    yibo authored
     
    Browse File »