Skip to content

fix(cloud): review rejection revokes app monetization — stop banned apps earning inference markup (#11870) - #11872

Merged
lalalune merged 1 commit into
developfrom
fix/monetization-off-on-review-reject
Jul 3, 2026
Merged

fix(cloud): review rejection revokes app monetization — stop banned apps earning inference markup (#11870)#11872
lalalune merged 1 commit into
developfrom
fix/monetization-off-on-review-reject

Conversation

@NubsCarson

Copy link
Copy Markdown
Member

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-135 says "A rejected re-review DOES cut everything off." It didn't:

  • runAppReview on a ban set review_status = 'rejected' but never touched monetization_enabled.
  • The creator-earnings path (deductCredits / reconcileCredits / processPurchase in app-credits.ts) gates only on monetization_enabled — only NEW paid charges check isAppMonetizationApproved (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/completions call, stayed publicly usable (the chat access rule treats monetization_enabled=true as public), and kept purchase share from pre-rejection charge requests.

The fix (smallest correct, composes with #11828/#11843)

  1. runAppReview: a rejection now flips monetization_enabled = false in the same transaction that writes review_status. Pricing fields are preserved; re-enabling goes back through PUT /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.
  2. Defense-in-depth for rows already persisted rejected+enabled (before this deploys): all earnings-math config assemblies + earnings guards now derive their effective flag from isAppMonetizationActive(app) = monetization_enabled && review_status !== 'rejected' (new pure predicate in app-credit-math.ts), including the cached LLM hot-path markup config (getCostMarkupConfigrunAppReview already invalidates that key on every decision).

Deliberately narrower than isAppMonetizationApproved: the draft re-gate (listing changed, re-review pending) keeps accruing markup on existing usage — that grandfather behavior is an explicit, documented product DECISION at api/v1/apps/[id]/route.ts:129-135 ("hard-stopping it on every metadata edit would let a rename freeze a creator's live revenue"). Only rejected revokes. Moving the earnings path to full isAppMonetizationApproved would 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 as app-backup.test.ts, loud-fail on PGlite init):

  1. Control: approved + enabled app, deductCredits(baseCost=1) → user debited 1.25, creator redeemable-earnings ledger gains a 0.25 row, org balance drops 1.25.
  2. The ban: listing turns prohibited → real runAppReview (deterministic keyword pre-filter, no LLM) returns ban → DB row shows review_status='rejected' and monetization_enabled=false (the fix), markup % preserved.
  3. After: same call charges exactly 1.0, creator markup 0, ledger row count unchanged.
  4. Legacy bad rows (forced 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_enabled stays true after ban; legacy row still charges markup): 2 fail / 1 pass. With the fix: 3 pass / 0 fail.

bun test src/lib/services/__tests__/app-review-rejection-cuts-monetization.test.ts  → 3 pass / 0 fail
bun test src/lib/services/app-credit-math.test.ts src/lib/services/app-review.test.ts → 33 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/0

tsgo --noEmit clean for touched files; biome check clean on all 5 touched files.

N/A evidence

  • Real LLM trajectory: N/A — the rejection path under test is the deterministic keyword pre-filter (no model call); no prompts/providers/action routing changed.
  • Screenshots/video: N/A — no UI surface changed; this is a backend money-gate fix proven on the DB ledger.

Notes for reviewers

  • Money path — do NOT merge without maintainer review (@lalalune, this sits in your fix: gate app monetization on review #11828 lane; it's the re-review-rejection sibling of your create-time gate and does not overlap your diff — apps/route.ts untouched here).
  • No migration needed: the earnings-path predicate covers rows already in the rejected+enabled state.

[cloud-security]

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0531cf72-1f4a-4621-b34c-d66ae8d036ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/monetization-off-on-review-reject

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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]
@lalalune
lalalune force-pushed the fix/monetization-off-on-review-reject branch from ec49a9a to 073f4d9 Compare July 3, 2026 10:30

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@lalalune
lalalune merged commit 4dfda93 into develop Jul 3, 2026
35 of 41 checks passed
@lalalune
lalalune deleted the fix/monetization-off-on-review-reject branch July 3, 2026 10:31

@lalalune lalalune left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cloud/money (HIGH): review REJECTION never disables monetization — banned apps keep earning inference markup

2 participants