feat(coding-plans): add coding plan subscriptions backend#1887
feat(coding-plans): add coding plan subscriptions backend#1887jeanduplessis wants to merge 5 commits intomainfrom
Conversation
… and user-owns-key principle Audit identified 17 findings across 12 categories. Adds Definitions section, clarifies subscription model (recurring, credit-based, pre-purchased keys), adds boundary/failure/lifecycle rules, and ensures the spec reflects that assigned API keys belong to the user.
Add subscription lifecycle, pre-purchased key inventory, atomic credit deduction, billing cron, and GDPR soft-delete support for coding plans. New tables: coding_plan_subscriptions, coding_plan_key_inventory. New tRPC router with user (catalog, subscribe, cancel) and admin (uploadKeys, keyInventory, cancelSubscription) endpoints. Hourly billing cron handles renewals and cancel-at-period-end. Pricing is env-configurable per provider via CODING_PLAN_PRICE_* vars.
| } | ||
|
|
||
| const balance = user.total_microdollars_acquired - user.microdollars_used; | ||
| if (costMicrodollars > 0 && balance < costMicrodollars) { |
There was a problem hiding this comment.
WARNING: Balance check happens outside the transaction
This verifies the user balance before any row is locked, then blindly increments microdollars_used later in the transaction. Two concurrent purchases can both pass this pre-check and both deduct, which lets the account go negative and violates the spec's requirement that credit verification and activation be atomic.
| } | ||
|
|
||
| let byokId: string; | ||
| if (existingByok) { |
There was a problem hiding this comment.
WARNING: Re-subscribe can overwrite a user-managed BYOK key
existingSub only proves there is a canceled subscription row; it does not prove that existingByok is the old coding-plan key. If the user canceled, added their own BYOK key for the same provider, and then re-subscribed, this branch updates that manual key in place and silently replaces it with the inventory key.
|
|
||
| const balance = row.total_microdollars_acquired - row.microdollars_used; | ||
|
|
||
| if (balance >= costMicrodollars) { |
There was a problem hiding this comment.
WARNING: Renewal balance is computed from a stale snapshot
sweepRenewals() joins kilocode_users once up front, so every due subscription for the same user sees the same starting balance. If two coding plans renew in the same sweep and the user only has enough credits for one, both rows still enter this branch and both deductions succeed, which can overdraw the account.
Code Review SummaryStatus: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Fix these issues in Kilo Cloud Other Observations (not in diff)None. Files Reviewed (15 files)
Reviewed by gpt-5.4-20260305 · 1,313,263 tokens |
Summary
/api/cron/coding-plans-billingroute to renew subscriptions from credits, trigger auto-top-up once per period, cancel subscriptions on insufficient balance, and remove BYOK keys when access ends.Verification
pnpm test -- src/lib/coding-plans/index.test.ts src/lib/user.test.tspnpm lintpnpm typecheckVisual Changes
N/A
Reviewer Notes
src/lib/coding-plans/index.ts: initial activation/reactivation and active-plan extension intentionally use different credit transaction idempotency keys.packages/db/src/migrations/0071_majestic_pestilence.sqland the snapshot are generated migration artifacts for the new coding plan tables and indexes.