Skip to content

feat(billing): expire new paid credits one year after purchase - #7609

Merged
iscekic merged 42 commits into
mainfrom
feat/paid-credit-expiry-impl
Oct 26, 2026
Merged

iscekic merged 42 commits into
mainfrom
feat/paid-credit-expiry-impl

Conversation

@iscekic

@iscekic iscekic commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

New paid credits can expire one calendar year after purchase. The policy is off by default: until PAID_CREDIT_EXPIRATION_EFFECTIVE_AT is set, every new purchase keeps legacy (non-expiring) terms and the purchase flows show no new expiry claim.

When activated:

  • New paid credits expire one calendar year (UTC) after purchase; February 29 clamps to February 28.
  • Each personal and organization Kilo Pass monthly allotment expires one year after its own issuance. Annual terms paid before activation keep legacy terms until renewal.
  • Apple in-app purchases never expire, including residual App Store subscriptions.
  • Existing balances, pending checkouts, and old receipts keep the terms they were sold under. Terms are captured at checkout or term start, not at webhook time.
  • Credits with the earliest deadline are spent first; non-expiring credits last.
  • Expiration and balance reads never trigger auto-top-up. Real usage and requested credit-funded services still can.

Accounting

  • Additive migration: paid_credit_policy, credit_source, source_transaction_id, consumed_microdollars, retired_microdollars, refund_principal_microdollars, plus three CONCURRENTLY indexes.
  • A new grant's expiry baseline is min(used, acquired), so credit that repays prior debt cannot expire later. original_baseline_microdollars_used keeps the usage prefix.
  • Expiry runs under a per-owner lock with a fresh primary read, from balance reads and a new /api/cron/expire-credits due-owner cron. User failures are isolated; organizations are processed in bounded batches.
  • Refunds and reversals are lot-aware: no double reclaim after expiry, no resurrection of expired credit, and a reversal restores repaid refund debt onto the lot that repaid it, keeping that lot's deadline. The reversal order no longer changes the result.
  • Parent-to-child organization transfers now work with expiring credits and keep each lot's deadline and source.
  • The KiloClaw Stripe settlement pass-through is marked settlement on both legs and never becomes spendable or reported revenue.
  • Reporting counts paid principal by policy (org Pass base included), uses the signed refund principal, and excludes expiry and internal bookkeeping rows.

Surfaces

  • Web: personal and org purchase options, both auto-top-up configurations, personal and org Pass, seat supplements, KiloClaw Pass checkout, confirmations, purchase history, emails, and admin grants. Direct saved-card purchases stay disabled until the policy has loaded.
  • Mobile: App Store packs say they don't expire; Play packs show the backend policy. New _1year_v2 Play SKUs are used only by updated clients after activation, and legacy receipts still verify and recover. Profile and purchase screens show the next expiring amount.
  • Translations for the new keys are in all mobile catalogs.

Not changed

  • The generic Stripe charge.refunded handler still does not claw back credits; that behavior is unchanged and out of scope.

Before activation

  • Create and approve the four _1year_v2 Google Play credit products.
  • Ship the mobile release with the new disclosures.
  • Merge the related docs, terms, and warehouse changes.
  • Set PAID_CREDIT_EXPIRATION_EFFECTIVE_AT to an announced future instant.

Verification

Run on an isolated VM with a fake account, local Postgres, and Stripe test mode:

  • @kilocode/db, @kilocode/web-shared, web, kilo-app, and kiloclaw-billing typecheck; web, mobile, db and billing lint; mobile unused-code and i18n checks; formatting.
  • Targeted web and web-shared Jest suites for credits, expiry, refunds, transfers, Pass, Stripe, store purchases, cron, funding and reporting all pass; mobile credit Vitest and billing lifecycle tests pass.
  • Policy unset: a Stripe grant and an Apple grant both stored as non-expiring; the credits page showed no expiry claim.
  • Policy set: the Stripe grant got a deadline one year out and the Apple grant stayed non-expiring. The credits page, Pass tier cards, and the Android profile card showed the matching terms and deadline.
  • Expiry cron on a partly used, past-due grant: the first authenticated call expired only the unused part, a second call changed nothing, an unauthenticated call was rejected, and no auto-top-up charge was created.

Adversarial verification and hardening

  • Bound expiration inserts without splitting the owner transaction, so long credit histories cannot exceed the database parameter limit.
  • Replace sequential baseline and consumption updates with set-based writes, and skip unchanged consumption rows.
  • Use refreshed acquired credits and usage together when returning child balances after reconciliation.
  • Exclude transfers and retirement of untracked legacy balances from paid-principal reporting.
  • Preserve remaining lot exposure during balance repair, including usage drift, legacy replay, and non-expiring lots. Repair adjustments remain bookkeeping.

Verified against isolated PostgreSQL with synthetic accounts:

  • Randomized usage, transfers, mixed deadlines, Apple exemptions, debt, repeated expiry, and final balance conservation against an independent model.
  • Concurrent purchase completion, refunds, and reversals; leap-day deadlines; oversized personal and organization expiry histories.
  • A real child-balance request blocked behind a concurrent usage transaction, then returned the correct post-expiry balance.
  • Authenticated HTTP cron calls drained a due-owner backlog across the run boundary. Concurrent replays created no duplicate closures or top-up issuance. Unauthenticated calls were rejected.
  • Direct reporting and balance-repair reproductions failed before the fixes and passed afterward.
  • Focused billing regression suites, affected-package type checks, changed-file lint, and formatting passed locally on the VM.

No live store purchase or production mutation was performed during this pass.

Final follow-up: the child-balance regression now verifies committed usage, and the legacy replay fixture includes complete funding and an explicit expiry closure. All CI checks passed on the final head. Kilobot recommends merge; human approval remains required.

End-to-end stress pass

A second pass drove grants, spend, expiry, refunds, transfers, repair, reporting, and the web, email and Android surfaces against isolated stacks with synthetic accounts, Stripe test mode, and an independent balance model. It found these issues, now fixed:

  • The organization expiry batch counted only credits_expired closures. A lot already closed by an Orb row failed the whole batch, so the cron returned 500 and stalled every due organization. The batch now uses the same closure categories as per-owner reconciliation. The cron also falls back to per-organization reconciliation when a batch fails.
  • A refund after a lot's deadline subtracted earlier refunds and balance resets a second time, and several partial refunds each subtracted the full expired amount. Expired principal now offsets refunded principal once per lot, and the result does not depend on whether the deadline passed between steps.
  • Balance repair rebuilt debt-repaid exposure for transfer chunks, organization admin grants, and organization Pass grants, so repaid debt became expirable again. Repair now treats source-linked lots as tracked, and those grants record a source.
  • The reachable seat dialog did not disclose Kilo Pass credit expiry and did not wait for the policy before adding seats. The admin top-up email preview always showed a deadline.

Known and accepted:

  • Auto-routing and the container usage meter read the raw balance without reconciling expiry. Credit past its deadline stays spendable there until the next balance read or cron run, about five minutes at most. The gateway always reconciles before it spends.
  • Google Play Kilo Pass base credits stay non-expiring. Store-billed Kilo Pass is being retired before activation.
  • Balance repair with positive usage drift can split expiry between an old untracked lot and a newer tracked lot differently from a full history replay. The effect is bounded by the drift, and repair is admin-only.
  • The revenue KPI counts organization Kilo Pass consumption debits as negative paid revenue. main does the same.

Stack readiness verification

  • Merged main and preserved the atomic promotional redemption cap with expiry reconciliation and refund-debt repayment. The concurrent integration regression passes.
  • Regenerated the additive migration as 0303_fine_moonstone; its DDL is unchanged, including concurrent-index transaction boundaries. Migration application and schema consistency checks pass.
  • Removed a redundant organization predicate from the expiry closure join. An isolated oversized history now completes its initial expiry and replay within the regression limit. No timeout was increased.
  • Repeated actual grants with the policy off and on against isolated PostgreSQL. Stripe grants follow captured terms; Apple grants remain exempt.
  • Repeated authenticated HTTP expiry cron calls. Only unused principal expires, replay changes nothing, unauthenticated calls are rejected, and no extra credit issuance occurs.
  • Focused billing regressions pass after the query fix. Database and web-shared typechecks, changed-source lint, and formatting pass.
  • The policy remains disabled unless configured. Store products, the mobile rollout, terms, and warehouse changes remain activation prerequisites, not requirements for merging disabled code. No production settings changed.

Latest base sync preserves both the paid-policy imports and native device-session invalidation. Router and credit-block regressions pass after this import-only conflict resolution. The live policy and balance routes pass against the isolated database. No billing behavior changed in this sync.

Latest conflict refresh

  • Merged the current main branch and retained paid-grant validation alongside the admin comment cleanup.
  • Regenerated the colliding migration as 0303_fine_moonstone; the complete SQL is byte-identical to the previous expiry migration.
  • Applied the complete migration chain to isolated PostgreSQL. All added credit columns and concurrent indexes are present; each index is valid and ready. A second generation reports no schema changes.
  • Focused admin grant, promotional grant, and paid-policy regressions: three suites and 38 tests pass. Database and web typechecks, conflict-file lint, and formatting pass.
  • No feature behavior changed in this refresh. The earlier browser evidence remains applicable; the browser exercise was not repeated.

Add a disabled-by-default paid-credit expiry policy. When PAID_CREDIT_EXPIRATION_EFFECTIVE_AT is set, new paid credits expire one calendar year after purchase, and each Kilo Pass allotment expires one year after issuance. Apple in-app purchases never expire. Existing balances and previously disclosed terms are unchanged.

Expiration spends the earliest deadline first, keeps refunds, reversals and transfers lot-aware, excludes KiloClaw settlement pass-through, and runs from a due-owner cron. Balance reads and expiration no longer trigger auto-top-up.

Purchase flows on web and mobile show the applicable terms, and receipts, history and emails show the stored deadline.
@iscekic iscekic self-assigned this Oct 15, 2026
@iscekic

iscekic commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

bot: E2E screenshots from the isolated VM (fake account, local Postgres, Stripe test mode). Each image was checked with verify_screen; all assertions passed.

Credits page with the policy unset: no expiry claim

Credits page with the policy set: one-year note and per-purchase deadlines

Kilo Pass tiers: allotment expiry and annual grandfathering

Android profile card: next expiring amount and date

@kilo-code-bot

kilo-code-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental head only adds clean merge resolutions and a small import/fixture fix on top of the previous review, with the migration renumbered to 0303_fine_moonstone byte-identically, so no new defects were found.

Assumptions: [INFERENCE] The range 27be16de..042e7b11 contains two upstream main merges (609 files) plus one follow-up commit; the merged main content was treated as already reviewed on main and checked only for preservation of this PR's billing behavior. [INFERENCE] No commands were executed, so lint, typecheck, and test results are taken from the PR description and not independently verified.

Files Reviewed (11 files)
  • apps/web/src/routers/user-router.ts - 0 issues; conflict resolution keeps both the paid-credit policy imports and invalidateDeviceSession, and both import sets are used (lines 152, 574).
  • packages/db/src/migrations/0303_fine_moonstone.sql - 0 issues; content is byte-identical to the pre-merge 0302_fine_moonstone.sql, COMMIT;/BEGIN; wraps the three CREATE INDEX CONCURRENTLY statements as in existing migrations (e.g. 0193_bitter_lightspeed.sql), and all added columns use DEFAULT where NOT NULL.
  • packages/db/src/migrations/meta/_journal.json - 0 issues; index 302 is main's 0302_loud_victor_mancha, index 303 is this PR's migration, with no duplicate idx.
  • packages/db/src/migrations/meta/0303_snapshot.json - 0 issues (generated); prevId chains to the 0302 snapshot and all new columns/indexes are present.
  • apps/web/src/app/admin/components/UserAdmin/UserAdminCreditGrant.tsx - 0 issues; resolution keeps the creditKind state and drops only a narration comment.
  • packages/web-shared/src/lib/ai-gateway/handlers/embeddings.ts - 0 issues; the restored isFreeModel import matches its use at line 281.
  • packages/web-shared/src/lib/ai-gateway/handlers/llm-proxy.ts - 0 issues; the added isFreeModel import matches its use at line 350, and the PR's requestCostLimit/boundedSpending/briefing-settlement logic is intact.
  • apps/web/src/tests/promotionalCredits.test.ts - 0 issues; the fixture now uses a persisted insertTestUser() and asserts the sanitized GRANT_MSG_UNEXPECTED_FAILURE message and Sentry extra fields.
  • packages/web-shared/src/lib/promotionalCredits.ts - 0 issues (targeted re-check); requirement checks including getCustomerInfo still run before the transaction opens, and the PR's creditKind, expiration baseline, reconcile, and refund-debt repayment fields remain.
  • packages/web-shared/src/lib/ai-gateway/processUsage.ts - 0 issues (targeted re-check); the auto-top-up after() registration remains inside the guarded block at line 712.
  • packages/web-shared/src/lib/creditExpiration.ts - 0 issues (targeted re-check); the organization batch still treats credits_expired, orb_credit_expired, and orb_credit_voided as closures.

The remaining 600+ files in the incremental range are upstream main content (current head vs main tip is exactly the 251-file PR diff), and were not re-reviewed here.

Previous Review Summaries (12 snapshots, latest commit 27be16d)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 27be16d)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental head deletes the redundant organization_id predicate from the organization expiry batch join — safe because closure rows join on original_transaction_id, which has a global unique index and is always written with its lot's owner — and otherwise only syncs main, so no new issues were found.

Assumptions: [INFERENCE] Only the range 19259141..27be16de was analyzed. The merge f0dcd0faa brings upstream main content (webhook-trigger schema, device auth, notification history, briefing accounting, promo-cap restructure) which was treated as already reviewed on main; it was checked only for whether it preserved this PR's billing logic, not re-reviewed line by line. store-subscription-completion.ts Google Play legacy terms remain unchanged and still carry the author's accepted explanation in the existing thread. The one changed line is a deletion, so any finding would have been summary-only; none was found.

Files Reviewed (12 files)
  • packages/web-shared/src/lib/creditExpiration.ts - 0 issues; the removed expiredCredits.organization_id = creditTransactionsTable.organization_id predicate is redundant. The join key original_transaction_id is a table-wide uniqueIndex (packages/db/src/schema.ts:379) and closures are written with the lot's own owner (packages/db/src/credit-expiration.ts:116), so no cross-organization or user/org match is possible; transfers use source_transaction_id, not original_transaction_id.
  • packages/web-shared/src/lib/promotionalCredits.ts - 0 issues; merge resolution keeps main's advisory-lock executeGrant structure while retaining the PR's creditKind/paid-policy fields, reconcileCreditExpirationsInTransaction, baseline, and refund-debt repayment. Requirement checks (including getCustomerInfo) still run before the transaction opens.
  • apps/web/src/tests/promotionalCredits.test.ts - 0 issues; merged in main's cap/refund-repayment tests alongside the PR fixtures.
  • packages/web-shared/src/lib/ai-gateway/handlers/llm-proxy.ts - 0 issues; conflict resolution adopts main's briefing-settlement accounting and hoists requestCostLimit with boundedSpending.
  • packages/web-shared/src/lib/ai-gateway/processUsage.ts - 0 issues; main's provider-specific is_byok change is preserved and the prior auto-top-up guard fix remains at line 712.
  • apps/web/src/routers/user-router.ts - 0 issues; conflict resolution adds main's notification-history procedure with bounds.
  • packages/db/src/schema.ts - 0 issues; keeps this PR's credit_transactions columns (consumed_microdollars, retired_microdollars, paid_credit_policy, credit_source, source_transaction_id, refund_principal_microdollars) plus main's tables; original_transaction_id unique index present.
  • packages/db/src/migrations/0302_fine_moonstone.sql - 0 issues; renumbered cleanly after main's 0297–0301, additive DEFAULT 0 NOT NULL columns, and the three CREATE INDEX CONCURRENTLY statements wrapped with COMMIT;/BEGIN;; journal index 302 matches.
  • ENVIRONMENT.md - 0 issues; keeps both the expiry env docs and main's Apple nonce variables.
  • packages/web-shared/src/lib/kilo-pass-org/consumption.ts - 0 issues (targeted re-check); original_baseline_microdollars_used still uses the real microdollarsUsed.
  • apps/web/src/lib/kilo-pass/checkout-session.ts - 0 issues (targeted re-check); paid_credit_policy remains in PRODUCT_METADATA_KEYS.
  • apps/web/src/routers/organizations/organization-admin-router.ts - 0 issues (targeted re-check); negative grants still use the stable organization_custom category.

Previous review (commit 1925914)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental head only removes an unused settings binding from the edit-completions handler, reformats the audio-transcriptions balance call, and corrects the expiryJitterHours doc to match its 0 default, so the previously reported documentation mismatch is resolved.

Assumptions: [INFERENCE] Only the incremental range 8f9b877f..19259141e was re-analyzed; the broader PR content was covered by earlier review passes, and the active comments on unchanged lines already carry author fix replies.

Files Reviewed (3 files)
  • packages/web-shared/src/lib/user/balance.ts - 0 issues; doc now matches the 0 default, formatting only otherwise.
  • packages/web-shared/src/lib/ai-gateway/handlers/edit-completions.ts - 0 issues; dropped settings is unreferenced (balance, plan, payer still used).
  • packages/web-shared/src/lib/ai-gateway/handlers/audio-transcriptions.ts - 0 issues; line-wrap only, settings still used at line 322.

Previous review (commit 8f9b877)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental work is a main merge plus one mobile test-clock pin; the merge resolutions preserve the PR's expiry logic, and the only open item is the previously reported stale jitter doc comment on the user balance read.

Assumptions: [INFERENCE] The incremental range (5db03a0b..8f9b877f0) includes ~506 files brought in from main by the merge commit; those mainline changes are treated as already reviewed, and only the merge conflict resolutions plus the new branch commit were analyzed. The fix link is present below for the carried SUGGESTION.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/web-shared/src/lib/user/balance.ts 23 expiryJitterHours now defaults to 0 while its doc comment (line 15) still says "Defaults to a random 0-1 draw"; the organization path says "Defaults to no jitter". A fix is already proposed in the active inline comment on this line.
Files Reviewed (11 areas)
  • apps/mobile/src/components/credits/credit-purchase-screen.tsx - 0 issues; merge kept the expiry disclosure, the extra skeleton line, and the new getCreditBlocks query.
  • apps/mobile/src/components/credits/credit-purchase-screen.mounted.test.tsx - 0 issues; resolution keeps the expiry-label assertions and the parseTimestamp mock.
  • apps/mobile/src/components/profile-credits-card.tsx - 0 issues; expiry row now delegates to CreditExpiryNote; removed imports are no longer referenced.
  • apps/mobile/src/components/profile-credits-card.mounted.test.tsx - 0 issues; expectations follow the extracted component.
  • apps/web/src/app/payments/topup/success/page.tsx - 0 issues; organization redirect now carries topup-transaction-id, matching OrganizationTopupSuccessHeader.
  • apps/web/src/app/payments/topup/success/page.test.ts - 0 issues; expectation matches the new query parameter.
  • apps/web/src/lib/kilo-pass/yearly-monthly-base-cron.ts - 0 issues; issuedAt: nextYearlyIssueAt is the per-month issuance instant, so catch-up allotments keep their own deadlines.
  • apps/mobile/src/i18n/locales/*.json (87 catalogs) - 0 issues; nextExpiry/expiresUtc present in all 87, removed bonusCreditsExpiring has no remaining references, no conflict markers.
  • services/kilo-mcp/catalog.json - 0 issues; adds read-only queries (user.getPaidCreditPolicy, credit-purchase reads), no new credential or mutation exposure.
  • ENVIRONMENT.md - 0 issues; documents expire-credits and PAID_CREDIT_EXPIRATION_EFFECTIVE_AT.
  • apps/mobile/src/lib/credits/credit-expiry.test.ts - 0 issues; pinned clock makes the fixture dates time-independent.

Fix these issues in Kilo Cloud

Previous review (commit 5db03a0)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The branch work since the previous review is a type-only follow-up (llm-proxy/organization-usage) plus a formatting change, and the new main merge preserves the PR's fixes; the only finding is a stale jitter-default doc comment left by the merge resolution.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/web-shared/src/lib/user/balance.ts 23 expiryJitterHours now defaults to 0 while its doc comment still says "Defaults to a random 0-1 draw"; the organization path was updated to "Defaults to no jitter".
Files Reviewed (16 files)
  • packages/web-shared/src/lib/ai-gateway/handlers/llm-proxy.ts - 0 issues; shared anonymous-balance constant typed as BalanceAndOrgSettings, no runtime change.
  • packages/web-shared/src/lib/organizations/organization-usage.ts - 0 issues; exported type and auto_top_up_enabled field match the .returning() selection at line 306.
  • apps/mobile/src/lib/credits/credit-expiry.ts - 0 issues; formatting-only.
  • apps/mobile/src/lib/credits/credit-expiry.test.ts - 0 issues; added elapsed-date case matches the helper.
  • apps/mobile/src/lib/credits/use-store-credit-products.ts - 0 issues; queryClient.query is the app's standard wrapper and supportsCreditExpiry is preserved.
  • apps/mobile/src/components/profile-credits-card.tsx - 0 issues; expiry rendering moved to CreditExpiryNote.
  • packages/web-shared/src/lib/user/balance.ts - 1 issue; stale jitter doc.
  • packages/web-shared/src/lib/creditExpiration.ts - 0 issues; targeted re-check confirms Orb closure categories and per-owner FOR NO KEY UPDATE locking remain.
  • apps/web/src/routers/kilo-pass-router.ts - 0 issues; merge keeps paid_credit_policy and assertChargeAllowed, with paidCreditTerms defined before use.
  • apps/web/src/components/profile/kilo-pass/KiloPassSubscriptionSettingsUpdatePanel.tsx - 0 issues; gating keeps both !policyReady and isPlanChangeBlocked.
  • apps/web/src/lib/kilo-pass/cancel-and-refund.ts, apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts, apps/web/src/lib/stripe/disputes.ts - 0 issues; imports resolve to the surviving helpers.
  • apps/web/src/routers/organizations/organization-admin-router.test.ts - 0 issues; conflict kept both imports.
  • apps/web/src/lib/kilo-pass/store-subscription-completion.ts - 0 issues; Google Play legacy terms remain the documented, accepted behavior.
  • packages/db/src/credit-expiration.ts - 0 issues (read for targeted verification).

Previous review (commit 0fae733)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only PR-authored change since the previous review is a spec clarification that Google Play Kilo Pass base credits, like App Store ones, never expire; it matches the implemented terms: 'legacy' behavior and resolves the one outstanding finding.

Scope Note

Reviewed commit 0fae733857 (.specs/kilo-pass.md, +2/-1). The origin/main merge (c7a79265c0) introduces no PR-feature code changes; its content is upstream and was not treated as new branch work. The previous Google Play warning is now resolved by the explicit spec rule in lines 164-165, which documents store-billed Pass retirement before activation, consistent with store-subscription-completion.ts:1036.

Files Reviewed (1 file)
  • .specs/kilo-pass.md - 0 issues; the added rule matches the code path that always passes terms: 'legacy' for store-billed Pass base credits.

Previous review (commit a82811b)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental follow-ups are sound, but one previously raised warning is still unresolved: Google Play Kilo Pass base credits are hardcoded to legacy terms, so post-activation Play purchases and renewals never receive the one-year expiry described in the spec.

Scope Note

Reviewed the six commits authored since the previous review (74fc1f81 → a82811bd) plus the conflict resolutions in the origin/main merge (0670072fc2). The new commits recognize Orb closures in the organization expiry batch, isolate organization failures in the cron, fix expired-principal offsetting across refunds, keep debt-repaid lot exposure through balance repair, disclose Pass expiry when adding seats, and follow the paid policy in the admin top-up email preview.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/kilo-pass/store-subscription-completion.ts 1036 Google Play Kilo Pass base credits still pass terms: 'legacy', and issueBaseCreditsForIssuance honors that override (packages/web-shared/src/lib/kilo-pass/issuance.ts:397-425). Post-cutoff Play purchases and renewals therefore never get the one-year term. This is already covered by an active inline comment, so no duplicate was posted.
Files Reviewed (13 files)
  • packages/web-shared/src/lib/creditExpiration.ts - 0 new issues; the batch join now excludes credits_expired, orb_credit_expired, and orb_credit_voided, matching per-owner reconciliation.
  • packages/web-shared/src/lib/creditExpirationOrg.test.ts - 0 issues; covers both Orb closure categories.
  • apps/web/src/app/api/cron/expire-credits/route.ts - 0 new issues; per-organization fallback runs only after the batch transaction fails and is idempotent.
  • apps/web/src/app/api/cron/expire-credits/route.test.ts - 0 issues.
  • packages/web-shared/src/lib/creditAdjustments.ts - 0 new issues; prior refunds are offset once, reversals after the deadline rejoin the expired pool, and zero-principal refunds are correctly excluded.
  • packages/web-shared/src/lib/creditAdjustments.test.ts - 0 issues.
  • apps/web/src/lib/user/recompute-balances.ts - 0 new issues; source_transaction_id is now part of the tracked-lot predicate and the field is fetched.
  • apps/web/src/lib/user/recompute-balances.test.ts - 0 issues.
  • apps/web/src/lib/recomputeOrganizationBalances.ts - 0 new issues; same tracked-lot predicate on the organization path.
  • apps/web/src/lib/credit-lot-repair.test.ts - 0 issues.
  • apps/web/src/lib/kilo-pass-org/service.ts - 0 new issues; credit_source falls back to promotion only when no paid policy is supplied.
  • apps/web/src/routers/organizations/organization-admin-router.ts - 0 new issues; same fallback for admin grants.
  • apps/web/src/components/subscriptions/seats/SeatsDetail.tsx - 0 new issues; notice and save gating are limited to seat increases on active Pass.
  • apps/web/src/routers/admin/email-testing-router.ts - 0 issues; preview follows the current policy.

Also verified the merge conflict resolutions in getBalanceForUser/getBalanceForOrganizationUser keep the intended "balance reads never auto-top-up" behavior while preserving account-access helpers.

Fix these issues in Kilo Cloud

Previous review (commit d50974f)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental update is test-only: it makes the legacy-expiration replay fixture coherent (refreshed acquired total, an extra non-expiring lot, an explicit credits_expired category) and tightens the organization child-balance assertion; no new issues were found.

Scope Note

Reviewed the changes authored since the previous review (a45b7bb339 → d50974fd98), i.e. commit d50974fd98 ("test(billing): prove concurrent usage and coherent replay fixtures"). Both changed files are test fixtures/assertions and the surrounding production code was re-checked only to validate the new expectations.

Files Reviewed (2 files)
  • apps/web/src/lib/credit-lot-repair.test.ts - 0 issues; new fixture still exercises legacy replay with a coherent ledger and the expiry amount matches accounting_error_mUsd - balance.
  • apps/web/src/routers/organizations/organization-funds-router.test.ts - 0 issues; getOrg selects exactly the two asserted columns, and the post-reconciliation values match reconcileCreditExpirationsInTransaction.

Previous review (commit a45b7bb)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental update hardens paid-credit expiry accounting under load (jsonb-batched row updates, 1000-row insert chunking, normalized-lot-aware balance repair, and a tighter paid-principal predicate) and adds regression tests; no new issues were found.

Scope Note

Reviewed the changes this branch authored since the previous review (b4732e33 → a45b7bb339): commit 0cb4eea6ab and the formatting-only a45b7bb339. The merge of main (83dab2d77d) contributes only upstream main content to this branch's files and was not treated as new PR-authored code.

Files Reviewed (13 files)
  • .specs/kilo-pass.md
  • apps/web/src/lib/credit-lot-repair.test.ts
  • apps/web/src/lib/recomputeOrganizationBalances.ts
  • apps/web/src/lib/user/recompute-balances.ts
  • apps/web/src/lib/user/recompute-balances.test.ts
  • apps/web/src/routers/organizations/organization-funds-router.ts
  • apps/web/src/routers/organizations/organization-funds-router.test.ts
  • packages/db/src/credit-expiration.ts
  • packages/web-shared/src/lib/creditAdjustments.ts
  • packages/web-shared/src/lib/creditExpiration.ts
  • packages/web-shared/src/lib/creditTransactions.test.ts
  • packages/web-shared/src/lib/paidCreditAccounting.test.ts
  • packages/web-shared/src/lib/paidCreditReporting.ts

Previous review (commit b4732e3)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only incremental change since the previous review corrects the transfer-in lot's original_baseline_microdollars_used to the recipient's true usage prefix and aligns the matching test helper; no new issues were found and no previous findings remain unresolved.

Files Reviewed (2 files)
  • packages/web-shared/src/lib/creditAdjustments.ts
  • packages/web-shared/src/lib/creditAdjustments.test.ts

Previous review (commit ffecafd)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the previous review is a one-line reorder in grant() that keeps the computed expiry_date and expiration_baseline_microdollars_used consistent while preserving current-caller behavior; no new issues were found and all previously reported findings remain resolved.

Files Reviewed (1 file)
  • apps/web/src/lib/kilo-pass-org/service.ts - 0 issues

Previous review (commit 81a1a4d)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The eleven follow-up commits since the last review resolve all seven previously reported findings, and each fix is correct, internally consistent, and backed by a targeted test; no new issues were found in the changed lines.

Verification of previous findings (all fixed)
  • organization-funds-router.ts — distribute now validates recipients (requireDirectChildren) before lockCreditOwners and re-checks after locking, so a bad recipient yields BAD_REQUEST rather than a Credit owner not found 500.
  • kilo-pass-org/consumption.ts — unlocked bonus stores the organization's real microdollars_used in original_baseline_microdollars_used (only expiration_baseline_microdollars_used keeps min(used, acquired)).
  • kilo-pass-router.ts / checkout-session.ts — paid_credit_policy added to PRODUCT_METADATA_KEYS and to sessionMetadata, with the captured terms passed into session creation; a pre-activation open session is now expired instead of reused.
  • organization-admin-router.ts — negative grants use the stable organization_custom category.
  • ai-gateway/processUsage.ts — auto-top-up registration is wrapped in the same try/catch guard as scheduleUsageEnqueue, so missing request scope warns instead of reporting a spurious failure.
  • promotionalCredits.ts — requirement checks and the redemption-count read now run outside the grant transaction; only the writes are transactional.
  • creditExpiration.ts — processLocalExpirations / processOrganizationExpirations return the refreshed state (including next_credit_expiration_at) instead of null when only the pointer changed.
Files Reviewed (18 changed files + generated)
  • packages/web-shared/src/lib/creditExpiration.ts — 0 issues
  • packages/web-shared/src/lib/promotionalCredits.ts — 0 issues
  • packages/web-shared/src/lib/kilo-pass-org/consumption.ts — 0 issues
  • packages/web-shared/src/lib/ai-gateway/processUsage.ts — 0 issues
  • apps/web/src/routers/organizations/organization-funds-router.ts — 0 issues
  • apps/web/src/routers/organizations/organization-admin-router.ts — 0 issues
  • apps/web/src/routers/kilo-pass-router.ts — 0 issues
  • apps/web/src/lib/kilo-pass/checkout-session.ts — 0 issues
  • apps/web/src/lib/kilo-pass/checkout-session.test.ts — 0 issues
  • apps/web/src/routers/kilo-pass-router.test.ts — 0 issues
  • apps/web/src/lib/kilo-pass-org/consumption.test.ts — 0 issues
  • apps/web/src/routers/organizations/organization-funds-router.test.ts — 0 issues
  • apps/web/src/routers/organizations/organization-admin-router.test.ts — 0 issues
  • apps/web/src/tests/promotionalCredits.test.ts — 0 issues
  • packages/web-shared/src/lib/creditExpiration.test.ts, creditExpirationOrg.test.ts, paidCreditAccounting.test.ts — 0 issues
  • packages/web-shared/src/lib/ai-gateway/usage-shadow-integration.test.ts — 0 issues
  • packages/db/src/migrations/0281_petite_jocasta.sql — 0 issues (additive DEFAULT 0 NOT NULL columns; CONCURRENTLY indexes with COMMIT;/BEGIN; boundaries present)
  • services/kilo-mcp/catalog.json, .kilo/skills/kilo-mcp/SKILL.md, migration snapshot/journal — generated, not reviewed

Previous review (commit 4fe4a35)

Status: 7 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 4
Issue Details (click to expand)

WARNING

File Line Issue
`apps/web/src/routers/organizations/organization-fund

[Snapshot truncated.]

Additional previous summary content was truncated to keep this comment within platform limits.


Reviewed by deepseek-flash-latest · Input: 65.2K · Output: 11.4K · Cached: 842.6K

Review guidance: REVIEW.md from base branch main

iscekic and others added 17 commits October 15, 2026 21:31
…action

Grant success tests now use persisted users because grants reconcile the owner row.
…ry-impl

# Conflicts:
#	.kilo/skills/kilo-mcp/SKILL.md
#	packages/db/src/migrations/meta/0280_snapshot.json
#	packages/db/src/migrations/meta/_journal.json
…ry-impl

# Conflicts:
#	.kilo/skills/kilo-mcp/SKILL.md
#	apps/web/vercel.json
#	packages/db/src/migrations/meta/0283_snapshot.json
#	packages/db/src/migrations/meta/_journal.json
#	packages/web-shared/src/lib/ai-gateway/typesafe/handler.ts
#	packages/web-shared/src/lib/organizations/organization-usage.ts
#	packages/web-shared/src/lib/user/balance.ts
…ry-impl

# Conflicts:
#	packages/db/src/migrations/meta/0287_snapshot.json
#	packages/db/src/migrations/meta/_journal.json
#	packages/web-shared/src/lib/ai-gateway/handlers/audio-transcriptions.ts
#	packages/web-shared/src/lib/ai-gateway/handlers/embeddings.ts
#	packages/web-shared/src/lib/ai-gateway/typesafe/handler.test.ts
#	packages/web-shared/src/lib/ai-gateway/typesafe/handler.ts
@iscekic
iscekic added this pull request to stack #8863 October 24, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants