fix(cloud): review rejection revokes app monetization — stop banned apps earning inference markup (#11870) - #11872
Conversation
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…pps earning inference markup (#11870) A re-review BAN set review_status='rejected' but left monetization_enabled true, and the creator-earnings path (deductCredits/reconcileCredits/ processPurchase) gates on that flag alone — only NEW paid charges checked isAppMonetizationApproved. A rejected (prohibited-category) app therefore kept collecting inference markup on every chat/generate-image/messages call and stayed publicly usable, contradicting the invariant documented at api/v1/apps/[id]/route.ts ("a rejected re-review DOES cut everything off"). - runAppReview: a rejection now flips monetization_enabled=false in the same transaction (pricing preserved; re-enable requires fresh approval via PUT /apps/:id/monetization). Composes with the create-time gate (#11828) and the restore gate (#11834/#11843). - Earnings math derives its effective flag from isAppMonetizationActive (enabled AND not rejected) so rows persisted rejected+enabled before this fix earn nothing either; the draft re-gate deliberately keeps accruing per the documented grandfather DECISION. - Real-PGlite ledger proof: approved+enabled earns 25% markup; a re-review ban (real runAppReview, deterministic pre-filter) revokes the flag and later calls earn ZERO; legacy rejected+enabled rows earn nothing (markup + purchase share). Red against pre-fix source, green with the fix. Refs #11834 #11843. Closes #11870. [cloud-security]
ec49a9a to
073f4d9
Compare
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
lalalune
left a comment
There was a problem hiding this comment.
Verified the review-rejection monetization fix locally in an isolated worktree. Focused checks passed: PGlite money-path proof for rejection cutting monetization, app-credit-math/app-review tests, packages/cloud/shared typecheck, and packages/cloud/shared lint. The implementation correctly gates effective earnings on enabled AND not rejected, flips monetization_enabled off on rejection, and preserves pricing fields for later re-approval.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Closes #11870. Refs #11834 / #11843 / #11828 (composes with both gates — see below).
The bypass (HIGH, money — verified live on develop)
The invariant documented at
packages/cloud/api/v1/apps/[id]/route.ts:129-135says "A rejected re-review DOES cut everything off." It didn't:runAppReviewon abansetreview_status = 'rejected'but never touchedmonetization_enabled.deductCredits/reconcileCredits/processPurchaseinapp-credits.ts) gates only onmonetization_enabled— only NEW paid charges checkisAppMonetizationApproved(app-charge-requests.ts:228).So an app that enabled monetization while approved and was then banned (prohibited-category listing) kept collecting inference markup on every
/v1/apps/:id/chat,/v1/apps/:id/generate-image,/v1/messages,/v1/chat/completionscall, stayed publicly usable (the chat access rule treatsmonetization_enabled=trueas public), and kept purchase share from pre-rejection charge requests.The fix (smallest correct, composes with #11828/#11843)
runAppReview: a rejection now flipsmonetization_enabled = falsein the same transaction that writesreview_status. Pricing fields are preserved; re-enabling goes back throughPUT /apps/:id/monetization, which requires a fresh approval. With this, the flag can no longer be true without an approved review at ANY entry point: create (fix: gate app monetization on review #11828), restore (cloud/money (HIGH): /apps/backup/restore bypasses monetization review — monetize-without-approval on a fresh draft app #11834/fix(cloud): force monetization off on app backup restore — close review-gate bypass (#11834) #11843), enable (monetization/route.ts), and now re-review rejection.isAppMonetizationActive(app)=monetization_enabled && review_status !== 'rejected'(new pure predicate inapp-credit-math.ts), including the cached LLM hot-path markup config (getCostMarkupConfig—runAppReviewalready invalidates that key on every decision).Deliberately narrower than
isAppMonetizationApproved: thedraftre-gate (listing changed, re-review pending) keeps accruing markup on existing usage — that grandfather behavior is an explicit, documented product DECISION atapi/v1/apps/[id]/route.ts:129-135("hard-stopping it on every metadata edit would let a rename freeze a creator's live revenue"). Onlyrejectedrevokes. Moving the earnings path to fullisAppMonetizationApprovedwould break that documented decision, so this PR does not.Evidence (real DB, ledger-asserted — not cached endpoints)
New real-PGlite proof
packages/cloud/shared/src/lib/services/__tests__/app-review-rejection-cuts-monetization.test.ts(same harness asapp-backup.test.ts, loud-fail on PGlite init):deductCredits(baseCost=1)→ user debited 1.25, creator redeemable-earnings ledger gains a 0.25 row, org balance drops 1.25.runAppReview(deterministic keyword pre-filter, no LLM) returnsban→ DB row showsreview_status='rejected'andmonetization_enabled=false(the fix), markup % preserved.rejected+enabled=true, simulating pre-fix state): inference markup 0 and purchase share 0 on the ledger.Red→green proof: against pre-fix develop source the suite fails exactly on the exploit (
monetization_enabledstaystrueafter ban; legacy row still charges markup):2 fail / 1 pass. With the fix:3 pass / 0 fail.Neighboring money suites (each green, run per-process):
app-credits-ledger 26/0 · app-credits-idempotency 6/0 · app-credits-reconcile-double-refund 8/0 · app-credit-hold-concurrency 7/0 · app-backup 3/0 · app-chat-sweep-double-refund 8/0tsgo --noEmitclean for touched files; biome check clean on all 5 touched files.N/A evidence
Notes for reviewers
apps/route.tsuntouched here).[cloud-security]