Skip to content

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.

Known Issues & Architectural Risks ​

Problems discovered during audit. Not a refactoring proposal — documentation only.


Issue 1: DELETE Endpoint Exists But May Have Gaps ​

Evidence ​

  • Frontend: features/admin/user-detail/api/adminUserDetail.ts:36-37 calls DELETE /api/admin/submissions/:id
  • Backend: Route exists at admin.ts:3039-3073

Current Behavior ​

DELETE endpoint implemented with requireAdmin + auditLog. Deletes submission and calls checkAndCloseCampaign if was ACCEPTED.

Risk ​

  • No ownership check — any admin can delete any submission
  • No check for paidOut or paidViewsTotal — deleting a paid submission loses audit trail
  • ViewSnapshot and PayoutItem cascade delete (Prisma cascade) — may lose history

Confidence ​

Confirmed — route exists but has gaps


Issue 2: Banned Users Can Still Submit ​

Evidence ​

  • src/api/routes/submissions.ts — no ban check on POST /api/submissions
  • src/api/routes/admin.ts:1179 — ban creates IdentityBlacklist entries
  • src/utils/moderation.ts — phoneBlacklistValue, upsertBlacklistEntry

Current Behavior ​

Banned user (via Discord ID, email, phone, IP) can still submit videos.

Risk ​

Banned fraudsters continue submitting; moderation ineffective.

Confidence ​

Confirmed — no check in submission create


Issue 3: Email Gate Disabled in Two Places ​

Evidence ​

  • submissions.ts:270-284 — commented out
  • SubmitVideoModal.tsx:285-290 — commented out EmailGate wrapper

Current Behavior ​

Users can submit without verified email.

Risk ​

No email for payout notifications, tax form reminders, support.

Confidence ​

Confirmed — explicitly commented


Issue 4: Rejection Reason Not Stored on Submission ​

Evidence ​

  • admin.ts:1483 — reason from request body
  • admin.ts:1607 — reason only in AdminAuditLog.details.reason
  • Submission model has no rejectionReason field

Current Behavior ​

Deny/Flag reason only in audit log (hard to query/display).

Risk ​

Clipper never sees why denied; admin can't filter by reason.

Confidence ​

Confirmed — schema lacks field


Issue 5: Duplicate Status Logic (Frontend + Backend) ​

Evidence ​

  • Frontend: features/submissions/lib/status.ts — getSubmissionStatusTone
  • Frontend: SubmissionReviewRow.tsx:139-168 — getStatusBadge (duplicate mapping)
  • Backend: prisma/schema.prisma:331 — status string enum
  • Backend: validation/schemas.ts:90 — UpdateSubmissionStatusSchema enum

Current Behavior ​

Status strings duplicated in 4+ places; no single source of truth.

Risk ​

Adding status requires changes in multiple files; drift likely.

Confidence ​

Confirmed — grep shows 4+ locations


Issue 6: Legacy Discord Approval Code Still Present ​

Evidence ​

  • submissions.ts:37-124 — entire postToDiscordLogChannel function commented out
  • buttonHandler.ts:155-158 — returns "use dashboard" for submission buttons

Current Behavior ​

Dead code in repo; confusion for new developers.

Risk ​

Accidental re-enable; maintenance burden.

Confidence ​

Confirmed — explicitly commented


Issue 7: Two Coexisting Payout Flows (Legacy + Delta) ​

Evidence ​

  • Submission.paidOut + paidAmount + payoutId (legacy)
  • Submission.paidViewsTotal + paidAmountTotal + PayoutItem (delta)
  • Payout.status has both legacy and new values

Current Behavior ​

Both flows work; legacy excluded from delta via paidOut=false filter.

Risk ​

Complexity; double-payment bugs; confusing admin UI.

Confidence ​

Confirmed — schema + code


Issue 8: No Rate Limit on Submission Creation ​

Evidence ​

  • submissions.ts — no rate limiter on POST /api/submissions
  • Only 10-min cross-campaign cooldown per user

Current Behavior ​

User can spam submissions (limited only by campaign duplicate check).

Risk ​

API abuse, Apify cost burn, moderation queue flood.

Confidence ​

Confirmed — no userLimiter on route


Issue 9: Campaign Budget Pre-Check Race Condition ​

Evidence ​

  • admin.ts:1498-1515 — budget check BEFORE status update
  • admin.ts:1537-1572 — budget clamp AFTER status update
  • No transaction spanning both

Current Behavior ​

Two admins accept simultaneously → both pass pre-check → both clamp → budget exceeded.

Risk ​

Budget overspend; inconsistent frozenViewCount.

Confidence ​

Strong evidence — non-atomic check-then-act


Issue 10: Payout Item Badges Only at Payout Time ​

Evidence ​

  • processRequest.ts:150-156 — badges computed during rescrape
  • runTrackingTick.ts — no badge computation

Current Behavior ​

Fraud signals (low like ratio, view spike) only detected when user requests payout.

Risk ​

Fraudulent submissions tracked for 30 days before detection.

Confidence ​

Confirmed — code inspection


Issue 11: Manual View Count Priority Inconsistency ​

Evidence ​

  • campaignBudget.ts:46 — manualViewCount ?? frozenViewCount ?? currentViews
  • calculateSubmissionEarnings.ts:106 — same priority
  • rescrape.ts:142 — manualViewCount ?? frozenViewCount ?? outcome.viewsAtPayout
  • admin.ts:444 — stats: manualViewCount ?? frozenViewCount ?? currentViews

Current Behavior ​

Consistent priority: manual > frozen > current. But not documented as invariant.

Risk ​

Future change could break priority silently.

Confidence ​

Confirmed — consistent but implicit


Issue 12: Tracking Stop Conditions Scattered ​

Evidence ​

  • runTrackingTick.ts:150-152 — query excludes trackingStoppedAt, frozenViewCount, campaign.viewsFrozen
  • runTrackingTick.ts:178-187 — expiry check sets trackingStoppedAt
  • runTrackingTick.ts:131 — 3 failures sets FLAGGED + trackingStoppedAt
  • adminPayoutReview.ts:489-496 — payout reject/flag sets trackingStoppedAt

Current Behavior ​

Multiple code paths stop tracking; no single "stopTracking" function.

Risk ​

Inconsistent state; missed cleanup (e.g., nextPollAt not nulled in all paths).

Confidence ​

Strong evidence — multiple mutation sites


Issue 13: Cross-Campaign Duplicate Cooldown Only 10 Min ​

Evidence ​

  • submissions.ts:325-338 — RECENT_DUPLICATE_WINDOW_MS = 10 * 60 * 1000

Current Behavior ​

User can re-submit same video to different campaign after 10 minutes.

Risk ​

Low barrier to spam across campaigns.

Confidence ​

Confirmed — constant value


Issue 14: No Self-Review Prevention for Admins ​

Evidence ​

  • admin.ts:1495 — adminId = req.user!.discordId
  • No check if admin owns the submission

Current Behavior ​

Admin can accept/deny their own submission.

Risk ​

Conflict of interest; no audit trail of self-action.

Confidence ​

Suspected — no code preventing it


Issue 15: Submission.addedByAdmin Field Unused ​

Evidence ​

  • prisma/schema.prisma:339 — addedByAdmin Boolean @default(false)
  • No API to create admin-added submission
  • No frontend UI

Current Behavior ​

Field always false; dead code.

Risk ​

Schema drift; confusion.

Confidence ​

Confirmed — no write path


Issue 16: Submission.messageId Legacy Field ​

Evidence ​

  • prisma/schema.prisma:334 — messageId String?
  • submissions.ts:111-114 — commented Discord message ID update
  • No current use

Current Behavior ​

Always null.

Confidence ​

Confirmed — dead code


Issue 17: ViewSnapshot Created on Submission Create (Before Review) ​

Evidence ​

  • submissions.ts:515-527 — ViewSnapshot created at PENDING

Current Behavior ​

Snapshots exist for denied/flagged submissions (never accepted).

Risk ​

Chart noise; wasted storage; misleading admin charts.

Confidence ​

Confirmed — code path


Issue 18: campaign.viewsFrozen Not Checked on Submission Create ​

Evidence ​

  • submissions.ts:291 — checks campaign.active, acceptingSubmissions, isDeleted
  • Does NOT check viewsFrozen

Current Behavior ​

Can submit to frozen campaign (if acceptingSubmissions not yet false).

Risk ​

Submissions to frozen campaigns enter PENDING but never tracked.

Confidence ​

Strong evidence — missing check


Issue 19: Affiliate Sweep on Payout Approve (Not Send) ​

Evidence ​

  • adminPayoutReview.ts:545-557 — sweepCommissionsIntoPayout at approve
  • adminPayoutReview.ts:855 — reverse on terminal send failure only

Current Behavior ​

Referral commissions moved to payout at approve, not send. If send fails transiently, sweep stays.

Risk ​

Referrer "paid" for payout that hasn't sent; accounting mismatch.

Confidence ​

Confirmed — code inspection


Issue 20: Tax Form Snapshot Taken at Approve, Not Send ​

Evidence ​

  • adminPayoutReview.ts:514-521 — taxFormSnapshotId captured at approve
  • If form changes between approve and send, snapshot stale

Current Behavior ​

Year-end 1099 uses form at approve time.

Risk ​

Tax form mismatch if user updates between approve/send (days/weeks apart).

Confidence ​

Suspected — design choice, not bug


Issue 21: No Integration Test Coverage for Critical Flows ​

Evidence ​

  • No test files for runTrackingTick, processPayoutRequest, computeScrapeBudgetClamp
  • Unit tests only for pure functions (pollScheduler.test.ts, clipperGroups/domain.test.ts)

Current Behavior ​

Critical money/budget logic untested end-to-end.

Risk ​

Regressions in budget clamp, payout math, tracking.

Confidence ​

Confirmed — test directory scan


Issue 22: Apify Cost Tracking Not Enforced ​

Evidence ​

  • costTracker.ts records usage per user
  • No limit/alert/block on high spend

Current Behavior ​

Malicious user could burn Apify budget via payout rescrapes.

Risk ​

Uncontrolled API costs.

Confidence ​

Confirmed — tracker exists, no enforcement


Issue 23: YouTube Quota Not Monitored ​

Evidence ​

  • youtube.ts — batch calls, no quota tracking
  • No circuit breaker

Current Behavior ​

Quota exhaustion → all YouTube scrapes fail → submissions flagged after 3 failures.

Risk ​

Silent tracking degradation.

Confidence ​

Suspected — no quota handling code


Issue 24: Submission.duration Not Used for Earnings ​

Evidence ​

  • prisma/schema.prisma:336 — duration Int @default(0)
  • calculateSubmissionEarnings.ts — only uses videoType (short/long)

Current Behavior ​

Duration stored but not used; videoType from scrape determines rate.

Confidence ​

Confirmed — field exists, not used in earnings