Skip to content

Commit 839455f

Browse files
lexwhitingclaude
andcommitted
fix(web): (C) reconcile sessions take model — drop flat revenueSharePct double-take
finalizeSession's deferred/atomic branch took a flat 15% session fee (live DB default 85) then payout took progressively on top — a structural DOUBLE-TAKE vs the single progressive take model. Credit the FULL amount at settlement (platformFeeCents 0); the one progressive take stays at payout (meter-parity). Remove all dead revenueSharePct refs (meter free-tier overage block + dev-ops counter, meter-with-metadata/proxy/me/settings/email dead selects+joins, the recordInvocationAsync legacy param) and DROP developers.revenue_share_pct, resolving the schema(100)/DB(85) drift. Migration 0014 is generated NOT applied (founder-gated; apply post-deploy, expand/contract). Latent today (immediate-only sessions + unwired processSettlementBatch + dormant prod = no active loss); fixed before sessions carry real money. Gates: apps/web tsc 0 / vitest 4283 (4282-1+2) / build 0 / eslint 0; mcp 1898/1 unchanged; zero pricing/payout/meter-credit/rate-limit/crypto/mcp/sdk-python hunks. Pre-build audit R2 PLAN_READY 0-blocking; post-build FUNDS-SEAL CERTIFIED 0-blocking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ab24388 commit 839455f

21 files changed

Lines changed: 1682 additions & 101 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- (C) revenueSharePct take-model reconciliation (2026-06-07)
2+
-- Drop the legacy flat-take column `developers.revenue_share_pct`.
3+
--
4+
-- The flat revenueSharePct model was superseded by the progressive
5+
-- calculateTakeCents model (apps/web/src/lib/pricing.ts), applied once at
6+
-- payout. Chunk (C) removed the last divergent consumer (the sessions
7+
-- finalize flat fee) and every dead reference, so no deployed code reads
8+
-- this column. Expand/contract: ship the (C) code first (so live instances
9+
-- stop SELECTing the column), THEN apply this DROP. The column carried only
10+
-- a stale DEFAULT 85 (vs the ORM's 100) that fed no money math.
11+
--
12+
-- IF EXISTS makes the one-shot Supabase SQL Editor paste idempotent.
13+
-- Hand-written (NOT via drizzle-kit generate — drizzle/meta is intentionally
14+
-- partial: only 0000_snapshot.json + a 3-entry journal, so generate would
15+
-- diff against a stale snapshot and emit a wrong migration). Register the
16+
-- applied hash in scripts/bootstrap__drizzle_migrations.sql.
17+
--
18+
-- FOUNDER-GATED: apply via the Supabase SQL Editor after the (C) bundle
19+
-- deploys. Do NOT auto-apply.
20+
21+
ALTER TABLE "developers" DROP COLUMN IF EXISTS "revenue_share_pct";

apps/web/scripts/bootstrap__drizzle_migrations.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,18 @@ INSERT INTO "drizzle"."__drizzle_migrations" (hash, created_at)
108108
SELECT 'dabd3540ed03fff9bc3f90ef12747b5b7e25597ef831d7f96b17654996b7732a', 1779993698000
109109
WHERE NOT EXISTS (SELECT 1 FROM "drizzle"."__drizzle_migrations" WHERE hash = 'dabd3540ed03fff9bc3f90ef12747b5b7e25597ef831d7f96b17654996b7732a');
110110

111+
-- 0014_drop_revenue_share_pct (hand-written, not in journal; ships with (C) 2026-06-07 — apply the .sql via SQL Editor alongside seeding this row, AFTER the (C) bundle deploys)
112+
INSERT INTO "drizzle"."__drizzle_migrations" (hash, created_at)
113+
SELECT 'e720ecaacc420f2c5d4af891d096d23788a6ad68f279f331e87c2a9d29bf43df', 1780790400000
114+
WHERE NOT EXISTS (SELECT 1 FROM "drizzle"."__drizzle_migrations" WHERE hash = 'e720ecaacc420f2c5d4af891d096d23788a6ad68f279f331e87c2a9d29bf43df');
115+
111116
COMMIT;
112117

113118
-- POST-RUN VERIFICATION (run separately after the bootstrap):
114119
-- SELECT id, substr(hash, 1, 12) AS hash_prefix, created_at
115120
-- FROM "drizzle"."__drizzle_migrations"
116121
-- ORDER BY created_at;
117122
--
118-
-- Expected: 14 rows. MAX(created_at) = 1779993698000 (0013_developer_api_keys).
123+
-- Expected: 15 rows. MAX(created_at) = 1780790400000 (0014_drop_revenue_share_pct).
119124
-- All three journal folderMillis (1773459112883, 1776513600000, 1777737600000)
120125
-- are < MAX, so drizzle-kit migrate will correctly skip them.

apps/web/scripts/seed-admin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ async function main() {
4848
slug: "lexwhiting",
4949
supabaseUserId,
5050
tier: "enterprise",
51-
revenueSharePct: 97,
5251
publicProfile: true,
5352
publicBio: "Founder, Alerterra",
5453
})

apps/web/src/app/(dashboard)/dashboard/settings/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ interface DeveloperProfile {
3232
name: string | null
3333
slug: string | null
3434
tier: string
35-
revenueSharePct: number
3635
stripeConnectStatus: string
3736
stripeSubscriptionId: string | null
3837
balanceCents: number

apps/web/src/app/api/auth/developer/me/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export async function GET(request: NextRequest) {
3737
name: developers.name,
3838
slug: developers.slug,
3939
tier: developers.tier,
40-
revenueSharePct: developers.revenueSharePct,
4140
stripeConnectStatus: developers.stripeConnectStatus,
4241
stripeSubscriptionId: developers.stripeSubscriptionId,
4342
balanceCents: developers.balanceCents,

apps/web/src/app/api/proxy/[slug]/route.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ async function authenticateProxyRequest(
114114
developerId: string
115115
pricingConfig: unknown
116116
}
117-
developerRevenueSharePct: number
118117
}
119118
| { ok: false; error: NextResponse }
120119
> {
@@ -153,11 +152,9 @@ async function authenticateProxyRequest(
153152
proxyEndpoint: tools.proxyEndpoint,
154153
developerId: tools.developerId,
155154
pricingConfig: tools.pricingConfig,
156-
revenueSharePct: developers.revenueSharePct,
157155
})
158156
.from(apiKeys)
159157
.innerJoin(tools, eq(apiKeys.toolId, tools.id))
160-
.innerJoin(developers, eq(tools.developerId, developers.id))
161158
.where(eq(apiKeys.keyHash, keyHash))
162159
.limit(1)
163160

@@ -226,7 +223,6 @@ async function authenticateProxyRequest(
226223
developerId: row.developerId,
227224
pricingConfig: row.pricingConfig,
228225
},
229-
developerRevenueSharePct: row.revenueSharePct,
230226
}
231227
}
232228

@@ -1206,10 +1202,8 @@ async function handleMppProxy(
12061202
proxyEndpoint: tools.proxyEndpoint,
12071203
developerId: tools.developerId,
12081204
pricingConfig: tools.pricingConfig,
1209-
revenueSharePct: developers.revenueSharePct,
12101205
})
12111206
.from(tools)
1212-
.innerJoin(developers, eq(tools.developerId, developers.id))
12131207
.where(eq(tools.slug, slug))
12141208
.limit(1)
12151209

@@ -1482,10 +1476,8 @@ async function lookupToolBySlug(slug: string, requestId: string) {
14821476
proxyEndpoint: tools.proxyEndpoint,
14831477
developerId: tools.developerId,
14841478
pricingConfig: tools.pricingConfig,
1485-
revenueSharePct: developers.revenueSharePct,
14861479
})
14871480
.from(tools)
1488-
.innerJoin(developers, eq(tools.developerId, developers.id))
14891481
.where(eq(tools.slug, slug))
14901482
.limit(1)
14911483

@@ -1507,7 +1499,6 @@ async function lookupToolBySlug(slug: string, requestId: string) {
15071499
proxyEndpoint: toolRow.proxyEndpoint as string,
15081500
developerId: toolRow.developerId,
15091501
pricingConfig: toolRow.pricingConfig,
1510-
revenueSharePct: toolRow.revenueSharePct,
15111502
}
15121503
return { ok: true as const, toolRow: verifiedTool }
15131504
}
@@ -1578,7 +1569,6 @@ async function forwardAndBill(
15781569
proxyEndpoint: string
15791570
developerId: string
15801571
pricingConfig: unknown
1581-
revenueSharePct: number
15821572
},
15831573
paymentMethod: PaymentMethod,
15841574
costCents: number,

apps/web/src/app/api/sdk/meter-with-metadata/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,12 @@ export const POST = withCors(async function POST(request: NextRequest) {
133133
)
134134
}
135135

136-
// Get tool + developer to find revenue share percentage
136+
// Get the tool's developerId for crediting
137137
const [toolDev] = await db
138138
.select({
139139
developerId: tools.developerId,
140-
revenueSharePct: developers.revenueSharePct,
141140
})
142141
.from(tools)
143-
.innerJoin(developers, eq(tools.developerId, developers.id))
144142
.where(eq(tools.id, body.toolId))
145143
.limit(1)
146144

apps/web/src/app/api/sdk/meter/route.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,6 @@ import { logger } from '@/lib/logger'
1212
import { withCors, OPTIONS as corsOptions } from '@/lib/middleware/cors'
1313
import { suspiciousActivityEmail } from '@/lib/email'
1414
import { sendNotificationEmail } from '@/lib/notifications'
15-
import { getRedis, tryRedis } from '@/lib/redis'
16-
17-
/** Monthly ops limits per tier */
18-
const TIER_OPS_LIMITS: Record<string, number> = {
19-
standard: 50_000,
20-
builder: 200_000,
21-
starter: 200_000, // legacy alias — treat as builder
22-
growth: 200_000, // legacy alias — treat as builder
23-
scale: 2_000_000,
24-
enterprise: 10_000_000,
25-
}
26-
27-
/** Legacy — progressive take rate now calculated at payout time. See lib/pricing.ts */
28-
const OVERAGE_REVENUE_SHARE_PCT = 100 // developer keeps 100% at invocation time; take rate applied at payout
2915

3016
export const maxDuration = 60
3117
export { corsOptions as OPTIONS }
@@ -89,7 +75,6 @@ export const POST = withCors(async function POST(request: NextRequest) {
8975
const [toolDev] = await db
9076
.select({
9177
developerId: tools.developerId,
92-
revenueSharePct: developers.revenueSharePct,
9378
developerTier: developers.tier,
9479
})
9580
.from(tools)
@@ -101,39 +86,6 @@ export const POST = withCors(async function POST(request: NextRequest) {
10186
return errorResponse('Tool not found.', 404, 'NOT_FOUND')
10287
}
10388

104-
// ── Overage: progressive take rate applied at payout time, not here ─
105-
let effectiveRevenueSharePct = toolDev.revenueSharePct
106-
const tier = toolDev.developerTier ?? 'standard'
107-
const tierLimit = TIER_OPS_LIMITS[tier] ?? TIER_OPS_LIMITS.standard
108-
109-
if (effectiveRevenueSharePct === 100 && tier === 'standard') {
110-
// Free tier — check if over monthly ops limit using Redis counter
111-
const redis = getRedis()
112-
const now = new Date()
113-
const monthKey = `dev-ops:${toolDev.developerId}:${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
114-
const monthOps = await tryRedis(() => redis.get<number>(monthKey))
115-
116-
if (monthOps !== null && monthOps >= tierLimit) {
117-
// Over limit — apply platform fee on this invocation
118-
effectiveRevenueSharePct = OVERAGE_REVENUE_SHARE_PCT
119-
logger.info('meter.overage_fee_applied', {
120-
developerId: toolDev.developerId,
121-
tier,
122-
monthOps,
123-
tierLimit,
124-
effectiveRevenueSharePct,
125-
})
126-
}
127-
128-
// Increment the monthly ops counter (fire-and-forget)
129-
tryRedis(async () => {
130-
const ttl = 33 * 24 * 60 * 60 // 33 days — covers any month + buffer
131-
const exists = await redis.exists(monthKey)
132-
await redis.incr(monthKey)
133-
if (!exists) await redis.expire(monthKey, ttl)
134-
})
135-
}
136-
13789
// Apply tiered rate limit based on developer's plan
13890
const tieredRl = await checkTieredRateLimit(
13991
`sdk-meter:${body.consumerId}`,
@@ -310,7 +262,6 @@ export const POST = withCors(async function POST(request: NextRequest) {
310262
costCents: body.costCents,
311263
latencyMs: body.latencyMs ?? null,
312264
developerId: toolDev.developerId,
313-
revenueSharePct: effectiveRevenueSharePct,
314265
referralCode: body.referralCode,
315266
isFlagged: fraudResult.flagged,
316267
})

apps/web/src/lib/__tests__/db-schema.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ describe('developers table schema', () => {
5454
expect(schema.developers.tier).toBeDefined()
5555
})
5656

57-
it('has revenueSharePct column', () => {
58-
expect(schema.developers.revenueSharePct).toBeDefined()
59-
})
60-
6157
it('has stripeSubscriptionId column', () => {
6258
expect(schema.developers.stripeSubscriptionId).toBeDefined()
6359
})

apps/web/src/lib/__tests__/metering.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ describe('Metering', () => {
181181
costCents: 100,
182182
latencyMs: 50,
183183
developerId: 'd1',
184-
revenueSharePct: 95,
185184
})
186185
).not.toThrow()
187186
})

0 commit comments

Comments
 (0)