Skip to content

Add script to expire non-expiring free credits#1269

Open
iscekic wants to merge 20 commits intomainfrom
fix/expire-credits
Open

Add script to expire non-expiring free credits#1269
iscekic wants to merge 20 commits intomainfrom
fix/expire-credits

Conversation

@iscekic
Copy link
Contributor

@iscekic iscekic commented Mar 19, 2026

Summary

  • Adds d2026-03-18_expire-free-credits.ts script that sets 30-day expiry dates on free, non-expiring credit transactions
  • Credit categories and descriptions are driven by a reviewed spreadsheet; empty description matches any description for that category
  • Dry-run mode by default, --execute to write changes; outputs JSONL log with per-user balances and projected expirations for rollback

Test plan

  • Dry run completed successfully (170k users, $783k projected expiration)
  • Review JSONL output for correctness
  • Execute run with --execute flag

iscekic added 11 commits March 18, 2026 14:22
introduce configurable concurrency for expire-free-credits via p-limit
parse --concurrency, defaulting to 50 and validate values
increase default batch size to 10000 for throughput
create timestamped output and error log files under output/
log created file paths and active concurrency for visibility
…edits

exclude categories from expiration to avoid expiring credits
exclude list: orb_migration_accounting_adjustment, credits_expired
custom, usage_issue, feedback
…category first

Instead of scanning all users and filtering, the script now takes a
mandatory --category=<name> arg and queries credit_transactions by
category directly, then groups by user. Much faster for targeted runs.
…n pairs

- Replace --category param with embedded (category, description) pairs
  copied from the reviewed spreadsheet
- Empty description matches NULL or empty, specific description matches
  exactly — handles same category with different descriptions
- Expiry date is now 30 days from runtime instead of hardcoded
- Add per-category breakdown in summary output
- Improve progress logging clarity
ignore runtime artifacts produced by the Kilo agent in prettier
@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 19, 2026

Code Review Summary

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
src/scripts/d2026-03-18_expire-free-credits.ts 280 projected_balance_microdollars is logged before skipped credits are removed, so the JSONL audit entry can stay negative even when this run will not tag those credits.

Fix these issues in Kilo Cloud

Other Observations (not in diff)

No carried-forward issues. The previously reported baseline-direction bug is resolved in the latest commit.

Files Reviewed (2 files)
  • src/scripts/d2026-03-18_expire-free-credits.test.ts - 0 issues
  • src/scripts/d2026-03-18_expire-free-credits.ts - 1 issue

Reviewed by gpt-5.4-20260305 · 199,957 tokens

Copy link
Contributor

@markijbema markijbema left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean i cant verify correctness just by reviewing. I think I'd write some tests or run on a copy of the database to verify first

Covers: fully/partially/unspent users, any-description matching,
non-free exclusion, org-scoped exclusion, already-expiring exclusion,
wrong description, mixed credits, multiple matching credits, zero-amount,
existing next_credit_expiration_at LEAST, and multi-block projected
expiration correctness.
iscekic added 7 commits March 19, 2026 12:13
… comment

the code comment now uses EXPIRY_DATE instead of a fixed date
to clarify runtime behavior and enable easier configuration
of the expiry date used by the script
…cases

Verifies that original_baseline_microdollars_used correctly determines
whether free credits are covered by prior usage: free credits granted
after spending are not covered and expire fully, while free credits
granted before spending are covered and nothing expires.
…its across batches

Uses a subquery to select the next N distinct user IDs, then fetches
all matching credits for those users in a single query. Prevents the
previous row-level pagination from skipping credits when a user's rows
span a batch boundary. Test runs with --batch-size=1 to exercise this.
…Orb double-deductions

When Orb clawed back spent free credits (reducing total_microdollars_acquired),
the expiration simulation would still try to expire the full credit amount,
pushing ~1,610 users into negative balance. Now boosts expiration baselines
on newly-tagged credits so total expiration never exceeds the current balance.
- Add src/scripts/ to testPathIgnorePatterns (integration tests need
  POSTGRES_SCRIPT_URL which isn't available in CI)
- Replace non-null assertions with null checks / fallbacks
id: t.id,
amount_microdollars: t.amount_microdollars,
expiration_baseline_microdollars_used:
(t.original_baseline_microdollars_used ?? 0) + (baselineBoosts.get(t.id) ?? 0),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Baseline adjustment goes the wrong direction

computeExpiration() treats a larger expiration_baseline_microdollars_used as less of the credit having been consumed (usageEnd - baseline shrinks as the baseline rises). Adding the deficit here therefore increases the amount that expires instead of reducing it. In the new Orb clawback cases, 0 + 5 still expires the full $5, so the rerun can remain negative and the persisted baseline written below has the same problem.

…ve instead of boosting baselines

Baseline boosting was wrong — increasing a credit's baseline shifts its claim
window right, which *increases* expiration, not decreases it. With
microdollars_used=0 (Orb users), no baseline prevents full expiration.

New approach: simulate all expirations, compute headroom (balance minus existing
expirations), then only set expiry on credits that fit within headroom. Credits
that would cause over-expiration are skipped entirely.
user_id: user.id,
next_credit_expiration_at: user.next_credit_expiration_at,
current_balance_microdollars: currentBalance,
projected_balance_microdollars: projectedBalance,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Logged projected balance still includes skipped credits

projected_balance_microdollars is computed before creditsSkipped are removed from the plan. In the Orb clawback cases this field stays negative even though the script no longer tags those credits, so the JSONL audit output no longer matches the writes this run will actually make. Recompute the balance from creditsToExpire (or rerun the simulation with only those credits) before logging it.

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