Appearance
Historical snapshot archived 2026-09-25. This records an earlier review or plan, not current implementation or live ticket state. For current work, follow root AGENTS.md, the relevant BloxClips skill, and owning repository source/tests. Preserve approved decisions as evidence; verify their present authority before acting.
Investigation Notes
Audit trail maintained during documentation.
Confirmed
| Area | Finding | Evidence |
|---|---|---|
| Submission create | Validates URL, campaign, platform, duplicates, account ownership | submissions.ts:237-539 |
| Submission create | Returns 412 if LinkedSocialAccount missing | submissions.ts:443-452 |
| Submission create | Returns 403 if account owned by another user | submissions.ts:454-459 |
| Submission create | Creates PENDING + initial ViewSnapshot | submissions.ts:474-527 |
| Admin accept | Budget pre-check (100% hard block) | admin.ts:1498-1515 |
| Admin accept | Sets acceptedAt, nextPollAt=now | admin.ts:1518-1527 |
| Admin accept | Budget clamp may freeze campaign | admin.ts:1537-1572 |
| Admin accept | Auto-close at 95% | admin.ts:1574-1580 |
| Admin accept | Creates UserNotification | admin.ts:1582-1596 |
| Admin accept | Audit log | admin.ts:1599-1617 |
| Tracking tick | Runs every 30 min | scheduler.ts:32 |
| Tracking tick | 12h/24h adaptive cadence | pollScheduler.ts:13-17 |
| Tracking tick | YouTube batch (50), TikTok/IG Apify batch | runTrackingTick.ts |
| Tracking tick | Budget clamp per scrape | runTrackingTick.ts:74 |
| Tracking tick | 3 failures → FLAGGED + trackingStoppedAt | runTrackingTick.ts:121-137 |
| Tracking tick | Expiry at acceptedAt + trackingDurationDays | runTrackingTick.ts:178-187 |
| Payout request | Cooldown 24h, daily rescrape cap 200 | payouts.ts:279, 304 |
| Payout request | Creates Payout(REQUESTED) → async rescrape | payouts.ts:419-453 |
| Payout rescrape | Delta math: payableViews - priorPaidViews | processRequest.ts:146 |
| Payout rescrape | Budget clamp applied | rescrape.ts:88 |
| Payout approve | Bumps paidViewsTotal/paidAmountTotal | adminPayoutReview.ts:499-509 |
| Payout approve | Sticky status: REJECTED→DENIED, FLAGGED→FLAGGED | adminPayoutReview.ts:486-497 |
| Payout approve | Tax form snapshot + affiliate sweep | adminPayoutReview.ts:514-557 |
| Payout send | TOTP required, dispatch to rail | adminPayoutReview.ts:714-822 |
| Payout send fail | Terminal: reverts paid-totals, reverses affiliate | adminPayoutReview.ts:843-865 |
| Budget clamp | First-come-first-earned by createdAt | campaignBudget.ts:183-184 |
| Budget clamp | Burn multiplier = 1/(1-fee) | fees.ts:burnMultiplier |
| Earnings calc | Threshold gate (minViews) → $0 | calculateSubmissionEarnings.ts:126-133 |
| Account verification | Required per platform/account | LinkedSocialAccount unique constraint |
| Discord approval | Legacy code removed/commented | submissions.ts:37-124, buttonHandler.ts:155-158 |
| Email gate | Disabled in frontend + backend | SubmitVideoModal.tsx:285-290, submissions.ts:270-284 |
Suspected
| Area | Hypothesis | Why |
|---|---|---|
| Admin self-review | Admin can accept own submission | No ownership check in admin.ts:1459 |
| Tax form snapshot timing | Snapshot at approve may stale by send | adminPayoutReview.ts:514-521 captures at approve |
| Affiliate sweep timing | Sweep at approve, not send | adminPayoutReview.ts:545-557 |
| YouTube quota exhaustion | No monitoring/circuit breaker | youtube.ts has no quota handling |
| Apify cost enforcement | Tracker exists but no limits | costTracker.ts records only |
| Race condition budget accept | Pre-check + clamp not atomic | admin.ts:1498-1572 two separate operations |
| Tracking stop scattered | Multiple paths set trackingStoppedAt | runTrackingTick.ts, adminPayoutReview.ts |
| Frozen campaign submit | viewsFrozen not checked on create | submissions.ts:291 missing check |
| ViewSnapshot for denied | Created at PENDING, never cleaned | submissions.ts:515-527 |
| DELETE submission endpoint | Frontend calls, backend exists with gaps | admin.ts:3039 |
Unknown
| Question | Impact |
|---|---|
What happens if campaign deadline passes but active=true? | Scheduler closes it (scheduler.ts:48-55) |
Can trackingDurationDays be 0 or negative? | Schema: Int @default(30), no constraint |
How is creatorHandle populated for YouTube? | youtube.ts returns creatorHandle from API |
What if manualViewCount < frozenViewCount? | Priority: manual > frozen > current (consistent) |
Does customRate override for both short/long? | Yes, single field overrides campaign default |
Can customViewCap exceed campaign cap? | Yes, admin can set any value |
What if frozenViewCount set but campaign not frozen? | Possible via manual admin action |
How are previewVideoUrl/previewImageUrl used? | Frontend embed preview (SubmissionReviewRow) |
Is Submission.duration ever used? | Stored, not used in earnings |
What triggers Campaign.paused? | Manual admin, no auto |
Can acceptingSubmissions be true while viewsFrozen=true? | Yes, separate flags; tick checks both |
What happens to PayoutItem on payout REJECTED? | Stays with decision=REJECTED, submission DENIED |
How are ReferralCommission linked to payout? | sourcePayoutId unique on ReferralCommission |
Is there a max customRate? | Validation: 0-1000 (schemas.ts:79) |
What if ratePerK = 0? | Earnings = 0, budget not consumed |
Can platformFeeRate > 0.99? | Schema: max 0.99 |
Contradictions
| Contradiction | Location A | Location B |
|---|---|---|
| "Approved" vs "Accepted" | Frontend: getStatusBadge shows "Accepted" for ACCEPTED | Payout: APPROVED decision, COMPLETED status |
| "Rejected" vs "Denied" | Submission: DENIED | Payout: REJECTED status + REJECTED decision |
| "Pending" meaning | Submission: PENDING | Payout: REQUESTED/SCRAPING/READY_FOR_REVIEW/AWAITING_SEND |
| Email gate | Backend: commented out | Frontend: commented out (both disabled) |
| Discord approval | submissions.ts: commented function | buttonHandler.ts: returns "use dashboard" |
| Legacy vs delta payout | Submission.paidOut (legacy) | Submission.paidViewsTotal (delta) |
paidAmount vs paidAmountTotal | Legacy single amount | Delta cumulative |
ViewSnapshot for all statuses | Created at PENDING | Only ACCEPTED tracked thereafter |
trackingStoppedAt set on | Expiry, 3 failures, payout reject/flag | Multiple code paths |
frozenViewCount priority | manual > frozen > current | Consistent across 4+ locations |
campaign.viewsFrozen vs acceptingSubmissions | Separate flags | Both checked in tick query |
Questions for the Team
- Email gate: Intent to re-enable? If so, when?
- DELETE submission: Should admin be able to delete? Soft delete vs hard?
- Banned user submit: Add ban check to
POST /api/submissions? - Rejection reason on submission: Add
rejectionReasonfield toSubmissionmodel? - Self-review prevention: Block admin from reviewing own submissions?
- Legacy payout migration: Plan to migrate
paidOut=trueto delta flow? - Apify cost limits: Add daily/monthly caps per user?
- YouTube quota monitoring: Add quota tracking + alerting?
- Fraud badges in tracking tick: Compute badges continuously, not just at payout?
- Tracking stop consolidation: Single
stopTracking(submissionId, reason)function? - ViewSnapshot cleanup: Delete snapshots for DENIED/FLAGGED submissions?
- Frozen campaign submit check: Block submissions to
viewsFrozencampaigns? - Status enum: Move to Prisma enum or shared constant?
- Custom rate/cap validation: Prevent values that break budget math?
- Payout item badge action: Auto-flag items with
VIEW_SPIKE/LOW_LIKE_RATIO? - Tax form snapshot at send: Move snapshot capture to send time?
- Affiliate sweep at send: Move sweep from approve to send?
- Race condition budget: Wrap accept+clamp in transaction?
addedByAdminfield: Remove or implement admin manual add?messageIdfield: Remove legacy Discord field?- Integration tests: Priority for
runTrackingTick,processPayoutRequest? - Rate limit submission create: Add
userLimitertoPOST /api/submissions? - Cross-campaign cooldown: Increase from 10 min?
- Campaign
viewsFrozenre-open: Only at <95%? What if manual view reduction? durationfield: Remove or use for long/short classification?