fix(cloud): arm the affiliate collected-earnings clamp on /v1/embeddings (#12017 residual leg 2) [cloud-money] - #12060
Conversation
…ngs (#12017 residual) #12047 closed #12017 leg 1: the embeddings reserve now threads affiliateCode, so the upfront hold folds in the attacker-set markup (up to 1000%) and the request fails closed when the org cannot cover it. Leg 2 was still open: billUsage ran WITHOUT the reservation, so the #11976 collectedAffiliateEarnings clamp was a no-op on this route. estimateTokens is chars/4; CJK/emoji-heavy input tokenizes at >1.5x that, so the provider-reported actual cost can blow past even the affiliate-inclusive buffered hold. The overage debit then fails (uncollected_overage, credits.ts, no throw) while the affiliate is still credited the FULL nominal preAffiliateTotalCost x markup% - a smaller but still repeatable cashable mint of money the platform never collected, violating #11976's own invariant (affiliate earnings never exceed collected revenue). Fix: - Hand billUsage a settler-backed VIEW of the reservation whose reconcile routes through the route's existing first-call-wins settler, preserving the #10557 single-settle-owner invariant (no double-settlement is possible; the route's explicit settle becomes an idempotent safety net). billUsage now reconciles BEFORE its affiliate-earnings write, so the clamp sees the reconciliation and pays the affiliate only from COLLECTED markup - 0 on an uncollected_overage. - Pass the server-generated requestId (#11588) into the billUsage context so the affiliate-earnings dedupe sourceId is deterministic (ai_billing:usage:<requestId>) instead of legacy-random; the route's own #11588 comment claimed this wiring but it was never hooked up. - Update embeddings-credit-leak.test.ts to the new (stronger) contract: billUsage receives the settler-backed view, reconciles through it, and the ledger still settles exactly once. Regression proof (real route + real ai-billing/credits reserve/reconcile CTEs + real seeded ai_pricing_entries catalog row + real affiliates repo + real redeemable-earnings ledger on PGlite, loud pgliteReady guard) in embeddings-affiliate-clamp.integration.test.ts: - org funded to only the base hold -> 402 before the provider call, nothing minted (real-ledger proof of #12047's leg 1); - fully funded org -> affiliate still earns the full collected markup and the dedupe sourceId is requestId-keyed; - uncollectable overage -> affiliate paid exactly the collected markup (0 when nothing above the base cost was collected), never the nominal 10x. These two clamp tests are red on develop tip (post-#12047) and green with this change. Refs #12017 #12047 #11972 #11976 #10557 #11588. [cloud-security]
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 |
lalalune
left a comment
There was a problem hiding this comment.
Reviewed the money path against ai-billing and createCreditReservationSettler. The settler-backed reservation view is the right shape: billUsage reconciles before affiliate earnings, so collectedAffiliateEarnings now sees the reconciliation, while the route still owns first-call-wins settlement and the explicit settle remains idempotent. Passing requestId into billUsage also fixes the dedupe sourceId gap. The PGlite integration scenarios cover fail-closed reserve, legitimate full payout, zero collected markup, and partial collected markup.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Money-path — maintainer review requested, do not self-merge
Residual leg 2 of #12017 (#11972 class). #12047 (merged today) landed leg 1: the embeddings reserve now threads
affiliateCode, so the upfront hold folds in the attacker-set markup (up to 1000%) and the request fails closed when the org can't cover it. This PR closes the leg #12047 left open and adds the real-ledger PGlite proof for both legs.The residual hole (verified red on develop tip, post-#12047)
billUsageon the embeddings deferred-settle path is called without the reservation, so the #11976collectedAffiliateEarningsclamp (ai-billing.ts) is a no-op here —reservation/reconciliationareundefinedand the affiliate is credited the fullpreAffiliateTotalCost × markup%regardless of what was actually collected.That still mints when the actual cost blows past the (now affiliate-inclusive) buffered hold:
estimateTokensischars/4, and CJK/emoji-heavy input tokenizes at >1.5× that heuristic, defeating the 1.5×COST_BUFFER. Then:estimate × (1+markup) × 1.5is collected;provider_tokens × (1+markup)> reserve → overage debit fails on the drained org →adjustmentType: "uncollected_overage"(credits.ts, no throw);addEarnings(cashableredeemable_earnings.available_balance) stands at the full nominal markup — money the platform never collected, violating fix(cloud): pay affiliate earnings only from collected markup #11976's own invariant ("affiliate earnings never exceed collected revenue"). Repeatable per request.Fix
billUsage(v1/embeddings/route.ts):billUsagenow receives{ ...reservation, reconcile: (c) => settleOwner(c) }— its internal reconcile routes through the route's existing first-call-wins settler, preserving the Inference billing: embeddings reservation-leak on billUsage throw + /v1/chat org-less reservation parity (both LOW) #10557 single-settle-owner invariant (no double-settlement possible; the route's explicit settle becomes an idempotent safety net). SincebillUsagereconciles before its affiliate-earnings write, the clamp sees the reconciliation and pays the affiliate only from collected markup — 0 onuncollected_overage.requestIdinto thebillUsagecontext (cloud/money: affiliate-earnings dedupe keyed on client x-request-id → charged-but-never-credited drift (LOW; re-key on server billing id) #11588 alignment): the route's own comment says the server-generated requestId "feeds the affiliate dedupe sourceId", but it was never handed tobillUsage— the dedupe sourceId waslegacy_<random>and could never fire. Nowai_billing:usage:<requestId>, matching/v1/chat/completions.embeddings-credit-leak.test.tsupdated to the new (stronger) contract:billUsagereceives the settler-backed view (never the raw reservation), reconciles through it, and the ledger still settles exactly once.Regression proof — real money, not stubs
New
__tests__/embeddings-affiliate-clamp.integration.test.ts: drives the real route + realai-billing/creditsreserve→reconcile CTEs + a real seededai_pricing_entriescatalog row (no pricing mock) + real affiliates repo + realredeemable_earningsledger on PGlite (loudpgliteReadyguard, mirrorsdomains-buy-cross-app-replay.integration.test.ts). Complements #12047's stubbed-ledger guard suite:legacy_sourceId)collected − base($0.78, not $12)Verification (real runs)
node test/run-unit-isolated.mjs embeddings(the package's CI lane, process-per-file): all 5 embeddings files pass — new clamp suite (5/5), fix(cloud): affiliate markup minted cashable earnings via /v1/embeddings reserve omitting affiliateCode (#12017) [cloud-money] #12047'sembeddings-affiliate-reserve.test.ts(4/4),embeddings-credit-leak(8/8, incl. the updated contract pin),embeddings-route-billing(7/7),embeddings-optimistic-billing.route.tsreverted to develop tip, the 3 clamp/dedupe tests fail exactly as tabled above; restored, 5/5 green.biome checkclean on all 3 changed files;tsgotypecheck has zero errors in the changed files (remaining package errors are pre-existing transitive-resolution noise, perpackages/cloud/shared/CLAUDE.md).Closes nothing new (#12017 already closed by #12047); refs #12017 #12047 #11972 #11976 #10557 #11588.
Legit third-party affiliate earnings on fully-collected requests are unchanged (proven by the fully-funded scenario).
— [cloud-security]