Skip to content

fix(cloud): arm the affiliate collected-earnings clamp on /v1/embeddings (#12017 residual leg 2) [cloud-money] - #12060

Merged
lalalune merged 1 commit into
developfrom
nubs/12017-embeddings-affiliate-clamp
Jul 3, 2026
Merged

fix(cloud): arm the affiliate collected-earnings clamp on /v1/embeddings (#12017 residual leg 2) [cloud-money]#12060
lalalune merged 1 commit into
developfrom
nubs/12017-embeddings-affiliate-clamp

Conversation

@NubsCarson

Copy link
Copy Markdown
Member

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)

billUsage on the embeddings deferred-settle path is called without the reservation, so the #11976 collectedAffiliateEarnings clamp (ai-billing.ts) is a no-op here — reservation/reconciliation are undefined and the affiliate is credited the full preAffiliateTotalCost × markup% regardless of what was actually collected.

That still mints when the actual cost blows past the (now affiliate-inclusive) buffered hold: estimateTokens is chars/4, and CJK/emoji-heavy input tokenizes at >1.5× that heuristic, defeating the 1.5× COST_BUFFER. Then:

  • reserve = estimate × (1+markup) × 1.5 is collected;
  • actual = provider_tokens × (1+markup) > reserve → overage debit fails on the drained org → adjustmentType: "uncollected_overage" (credits.ts, no throw);
  • the affiliate addEarnings (cashable redeemable_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

  1. Settler-backed reservation view → billUsage (v1/embeddings/route.ts): billUsage now 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). Since billUsage reconciles before its affiliate-earnings write, the clamp sees the reconciliation and pays the affiliate only from collected markup — 0 on uncollected_overage.
  2. requestId into the billUsage context (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 to billUsage — the dedupe sourceId was legacy_<random> and could never fire. Now ai_billing:usage:<requestId>, matching /v1/chat/completions.
  3. embeddings-credit-leak.test.ts updated to the new (stronger) contract: billUsage receives 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 + real ai-billing/credits reserve→reconcile CTEs + a real seeded ai_pricing_entries catalog row (no pricing mock) + real affiliates repo + real redeemable_earnings ledger on PGlite (loud pgliteReady guard, mirrors domains-buy-cross-app-replay.integration.test.ts). Complements #12047's stubbed-ledger guard suite:

scenario develop tip (post-#12047) this PR
org funded to base-only hold → 402 fail-closed before the provider call, nothing minted (leg 1, real ledger) pass pass
fully funded org → affiliate earns full collected markup ($1.20 on the ledger), dedupe sourceId is requestId-keyed fail (legacy_ sourceId) pass
uncollectable overage, nothing above base collected → affiliate paid 0 (not the nominal $24) fail (paid nominal) pass
uncollectable overage, partial markup collected → affiliate paid exactly collected − base ($0.78, not $12) fail (paid nominal) pass

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's embeddings-affiliate-reserve.test.ts (4/4), embeddings-credit-leak (8/8, incl. the updated contract pin), embeddings-route-billing (7/7), embeddings-optimistic-billing.
  • Red-proof: with route.ts reverted to develop tip, the 3 clamp/dedupe tests fail exactly as tabled above; restored, 5/5 green.
  • biome check clean on all 3 changed files; tsgo typecheck has zero errors in the changed files (remaining package errors are pre-existing transitive-resolution noise, per packages/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]

…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]

@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: e3dae725-742a-4838-aa7b-f2a77f28aef1

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 nubs/12017-embeddings-affiliate-clamp

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.

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

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

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions github-actions Bot added the Tests label Jul 4, 2026
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.

2 participants