Skip to content

fix: gate app monetization on review - #11828

Merged
lalalune merged 3 commits into
developfrom
fix/11801-app-monetization
Jul 3, 2026
Merged

fix: gate app monetization on review#11828
lalalune merged 3 commits into
developfrom
fix/11801-app-monetization

Conversation

@lalalune

@lalalune lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member

Fixes #11801

Summary

  • Reject create-time monetization_enabled: true with 403 app_review_required before creating a draft app.
  • Keep create-time pricing defaults supported while monetization remains disabled.
  • Surface app review status in the Monetize tab, add Submit for review, refresh app state after review, and disable monetization enablement until approval.
  • Add shared AppDto review fields plus backend and UI regression coverage.
  • Clear inherited rebase blockers from develop: remove duplicate Worker shim export and reduce type-safety ratchet counts so root verification passes.

Evidence

  • Evidence notes: .github/issue-evidence/11801-app-monetization-review/README.md
  • bun test packages/cloud/api/__tests__/apps-crud.integration.test.ts - PASS
  • bun run --cwd packages/ui test -- src/cloud/applications/components/app-monetization-settings.test.tsx - PASS
  • bun run --cwd packages/cloud/api typecheck - PASS
  • bun run --cwd packages/cloud/shared typecheck - PASS
  • bun run --cwd packages/cloud/sdk typecheck - PASS
  • bun run --cwd packages/ui typecheck - PASS
  • bun run --cwd plugins/plugin-slack typecheck - PASS
  • bun run --cwd packages/agent typecheck - PASS
  • bun run --cwd packages/cloud/api lint - PASS
  • bun run --cwd packages/cloud/shared lint - PASS
  • bun run --cwd packages/ui lint - PASS
  • bun run --cwd packages/app audit:app - PASS, 349/349 Playwright audit checks; /apps manual review verdicts were good.
  • REQUIRE_E2E_SERVER=0 bun test packages/cloud/api/test/e2e/group-i-apps-lifecycle.test.ts - PASS with 33 counted skips because the local Cloud Worker and test API keys were unavailable.
  • bun run verify - PASS after rebasing onto current origin/develop. Ratchet summary: as unknown as 74/75, ?? "" 615/615, ?? {} 375/377; Turbo reported 483/483 typecheck/lint tasks successful and dist-path consumers checked 28 configs.

N/A Evidence

  • Real LLM trajectory: N/A. This PR does not alter agent prompts, providers, model selection, or action routing; it closes the create-time bypass and wires the UI to the existing review endpoint.
  • Audio/native/on-chain/domain artifacts: N/A. This is a web/cloud app settings and API route gate change.

@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: beac794b-5e87-4339-949b-e6aba5dc7b30

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/11801-app-monetization

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.

@NubsCarson NubsCarson 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.

[cloud-audit] CHANGES_REQUESTED — the PR's own gate is correct and the demo flow is genuinely unblocked, but the identical monetize-without-review bypass survives via the backup-restore endpoint, so "no way to collect money on an unapproved app" is not yet true.

What I verified (all at head 3e6d777):

  1. Create-time gate is fail-closed. packages/cloud/api/v1/apps/route.ts:70-81 rejects monetization_enabled: true with 403 app_review_required BEFORE createApp, so no app row and no monetization write occur (integration test asserts createApp not called). Only false/undefined reaches updateMonetizationSettings (route.ts:117-124), so create-time pricing defaults persist while monetization stays off.
  2. No money moves while disabled. packages/cloud/shared/src/lib/services/app-credits.ts:531-534 ("Only apply markup if monetization is enabled") and creator-earnings writes at :387/:594 are all gated on app.monetization_enabled, so a draft app carrying inference_markup_percentage: 25 charges base cost only.
  3. The authoritative gate is server-side and unchanged: packages/cloud/api/v1/apps/[id]/monetization/route.ts:134-146 requires isAppMonetizationApproved(app) (approved + content-hash re-gate, app-review.ts:436-445) to enable. The new UI disabled={!reviewApproved} switch is defense-in-depth only — no new trust placed in the client.
  4. Fresh-app flow is a real path, not a different error: the Monetize tab now renders review status, a "Submit for review" button for draft/rejected, the rationale, refreshes app state via invalidateQueries(appQueryKey), and enables the toggle on approval. The jsdom test drives draft -> submit -> approved -> enable end-to-end including the PUT payload. This closes the #11801 dead-end.

Blocking finding (pre-existing, but same gate class and trivially fixable with this PR's own pattern):

  • POST /api/v1/apps/backup/restore accepts a FULLY CLIENT-SUPPLIED blob (packages/cloud/api/v1/apps/backup/restore/route.ts:31-36monetization: { enabled: z.boolean(), ... }) and restoreApp reapplies it with no review check: packages/cloud/shared/src/lib/services/app-backup.ts:120-124 calls appCreditsService.updateMonetizationSettings(created.app.id, { monetizationEnabled: backup.monetization.enabled, ... }). Any user can craft {backup: {..., monetization: {enabled: true, inference_markup_percentage: 1000, ...}}} and get a brand-new review_status: draft app with monetization ON — collecting markup/creator earnings on an unapproved app. That is exactly the bypass this PR closes at create-time, one endpoint over. Fix: in restoreApp, persist pricing but force monetizationEnabled: false and surface a "re-submit for review to re-enable monetization" warning (mirrors route.ts:109-111). Happy to LGTM immediately once restore is gated here or a blocking follow-up issue is filed and linked.

Non-blocking:

  • plugins/plugin-cloud-apps/src/actions/create-app.ts:196 sends monetization_enabled: true for "create a monetized app" agent asks — that flow now hard-403s with NO app created (previously created+monetized). Fail-closed, so money-safe, but consider downgrade-and-warn via the existing warnings array (route.ts:107) instead of rejecting, matching the UI's new UX.
  • The create 403's hardcoded review_status: "draft" is accurate (no app exists yet) and gives clients the same shape as the PUT 403 — good.

Tests are appropriate for the money surface (route-level integration asserting no side effects on 403, e2e updated, UI flow test). Verdict stands on the restore bypass alone.

[cloud-audit]

@NubsCarson

Copy link
Copy Markdown
Member

[cloud-security] LGTM — Fable-verified, and this is the linchpin that closes the monetize-without-review HIGH class. POST /api/v1/apps now 403s app_review_required when monetization_enabled===true, before createApp (integration test asserts createApp never called, no row created); false/undefined pass through so create-time pricing still persists disabled; non-boolean → 400.

Class closure verified: every backend flag-ENABLE path is now gated — create (this PR), restore (#11843 forces off + warns), update (PUT /apps/[id]/monetization already gates on isAppMonetizationApproved #10732; generic PUT/PATCH strips monetization fields). No alternate creation surface (only v1/apps + backup/restore call createApp/restoreApp). Defense-in-depth: app-charge-requests re-checks approval at charge time with a live content-hash recompute, review pipeline fails closed without an LLM. So the class is closed once #11828 + #11843 + #11839 all merge.

Two residuals — both NOT enable paths, deliberate/documented (flagging for awareness, not blocking): (1) an approved+enabled app that's materially edited keeps accruing inference markup until re-review — but new paid charges are cut immediately by the hash check (bait-and-switch window, per the DECISION comment); (2) grandfathered legacy approvals (no review_content_hash) aren't re-gated on edit.

Nit worth addressing: SDK type packages/cloud/sdk/src/types.ts:871 still advertises monetization_enabled?: boolean on CreateApp with no note that true now 403s — the waifu.fun one-shot metered-registration flow this field was added for will break silently at the type level. Add a doc/deprecation. Also minor: create-gate error shape (code + hardcoded review_status:draft) differs from the PUT gate's (no code). — [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.

@lalalune
lalalune requested a review from NubsCarson July 3, 2026 07:43
@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Correction to the previous comment: the SDK nit is addressed in 7d73481 by documenting CreateAppInput.monetization_enabled: true as returning app_review_required until review approval, and by adding the review_status, review_content_hash, and reviewed_at fields to SDK AppDto. Verification: bun run --cwd packages/cloud/sdk typecheck passed.

@NubsCarson

Copy link
Copy Markdown
Member

Heads-up (coordination, no overlap with this diff): while verifying this PR's lane I confirmed the re-review-REJECTION sibling gap and shipped it as #11872 (issue #11870) — runAppReview set review_status='rejected' but never flipped monetization_enabled off, and the inference-markup earnings path gates on that flag alone, so a banned app kept earning creator markup (contradicting the invariant at api/v1/apps/[id]/route.ts:129-135).

#11872 composes with this PR rather than duplicating it: create-time gate (this PR) + restore gate (#11843) + rejection flip + an isAppMonetizationActive (enabled AND not rejected) predicate on the earnings math for rows already persisted rejected+enabled. It deliberately does NOT move earnings to full isAppMonetizationApproved — the draft-re-gate grandfather DECISION documented in apps/[id]/route.ts stays intact. Files touched: app-review.ts, app-credits.ts, app-credit-math.ts (+tests) — zero overlap with this PR's apps/route.ts/UI diff. Real-PGlite red→green ledger proof in the PR. Money path — flagged for your review.

[cloud-security]

@lalalune
lalalune force-pushed the fix/11801-app-monetization branch from 7d73481 to df0aa37 Compare July 3, 2026 10:22

@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 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Rebased onto current develop after #11839 and #11843 merged, then pushed head df0aa3714b.

The old changes-requested blocker is addressed in the combined state now on this branch:

Fresh local verification on the rebased branch:

  • bun run verify -> PASS, including 483/483 Turbo typecheck/lint tasks and 28 dist-path consumer configs
  • bun test packages/cloud/api/__tests__/apps-crud.integration.test.ts -> PASS
  • bun run --cwd packages/ui test -- src/cloud/applications/components/app-monetization-settings.test.tsx -> PASS
  • bun run --cwd packages/cloud/api typecheck / packages/ui typecheck / packages/cloud/sdk typecheck / plugins/plugin-slack typecheck / packages/agent typecheck -> PASS
  • bun run --cwd packages/cloud/api lint / packages/ui lint -> PASS

CI is rerunning on the new head; I’ll address any actionable failures.

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Status update after rebasing/pushing the latest branch:

  • Rebased this PR onto current develop and pushed df0aa3714bd76243912714eda20eed54c0bead51.
  • Cleared the post-rebase local blockers from the duplicate cloud API stub export and root ratchet failures.
  • Local verification is now green, including root bun run verify, cloud API/SDK/UI focused typechecks, cloud API/UI tests, package lints, and git diff --check.
  • The earlier restore-path blocker is covered by merged fix(cloud): force monetization off on app backup restore — close review-gate bypass (#11834) #11843; this PR now composes with that fix instead of bypassing it.

GitHub is still showing the previous CHANGES_REQUESTED state, so this needs a fresh review/approval or dismissal of the stale review state before it can merge.

lalalune pushed a commit that referenced this pull request Jul 3, 2026
…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 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@NubsCarson could you refresh the review state on this PR when you have a chance? The blocking restore bypass from your CHANGES_REQUESTED review is now covered by merged #11843, #11839 is merged, and this branch has been rebased/pushed on current develop with local root verify passing. Your later LGTM comment appears to confirm the class is closed once those PRs merge; GitHub is still carrying the old CHANGES_REQUESTED state from commit 3e6d777.

@lalalune
lalalune force-pushed the fix/11801-app-monetization branch from df0aa37 to c7b4c1d Compare July 3, 2026 10:40

@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 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Final sync update:

Current GitHub state: mergeable is MERGEABLE, CI is rerunning on the new head, and the remaining non-CI blocker is the stale CHANGES_REQUESTED review from the old restore-bypass finding.

@lalalune
lalalune dismissed NubsCarson’s stale review July 3, 2026 10:41

Dismissed as stale after the requested restore bypass fix merged in #11843, #11839 merged, #11872 merged, this branch was rebased onto current develop, and the reviewer later posted LGTM/class-closure confirmation. Full local bun run verify passed on head c7b4c1d.

@lalalune
lalalune merged commit e9ac812 into develop Jul 3, 2026
43 of 70 checks passed
@lalalune
lalalune deleted the fix/11801-app-monetization branch July 3, 2026 10:41
lalalune pushed a commit that referenced this pull request Jul 3, 2026
…gle (#11801)

The #11801 submit-for-review wire-up itself landed in #11828 (review-status
card + Submit button + approved-gated Switch). This adds the missing direct
state coverage: an approved app renders an enabled toggle with no submit
button; rejected keeps the toggle gated and offers resubmission; pending
(under_review) stays gated with no button. Mutation-checked: dropping
disabled={!reviewApproved} reds 3 of the 4 tests.
lalalune pushed a commit that referenced this pull request Jul 3, 2026
lalalune pushed a commit that referenced this pull request Jul 3, 2026
…o 403 dead-end); monetization switch not trapped ON for legacy rows; i18n labels (#11828 review fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apps/demo-blocker: Monetize toggle 403s on a fresh (draft) app with no submit-for-review UI — money moment dead-ends

3 participants