Skip to content

chore(credits): retire getGrantExpiresAt and the expires_at it feeds - #838

Merged
sweetmantech merged 3 commits into
mainfrom
chore/credits-retire-grant-expiry
Aug 18, 2026
Merged

chore(credits): retire getGrantExpiresAt and the expires_at it feeds#838
sweetmantech merged 3 commits into
mainfrom
chore/credits-retire-grant-expiry

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Follow-on cleanup for recoupable/chat#1960. Row 6 of that issue's PR matrix.

Merge after api#837 (the refill-is-a-floor fix). Branched from main independently — there is no file overlap with #837, only a logical dependency: until the refill can no longer reduce a balance, a grant genuinely does expire and removing the field would be a lie in the other direction.

getGrantExpiresAt.ts exists only to tell an admin when the monthly reset will destroy a grant. It is 14 lines of code under 20 lines of comment explaining which direction its month-clamping errs in, all of it reasoning about an overwrite that no longer happens once the refill is a floor. Worse than dead: it tells admins to re-grant credits that do not need re-granting.

What changes

Deleted: lib/credits/getGrantExpiresAt.ts and lib/credits/__tests__/getGrantExpiresAt.test.ts.

POST /api/admins/credits no longer returns expires_at. Response goes from 9 fields to 8:

{
  "status": "success",
  "grant_id": "...",
  "account_id": "...",
  "remaining_credits": 9999,
  "previous_credits": 12,
  "reason": "Trial headroom for the Aug 12 label demo",
  "granted_by": "...",
  "granted_at": "2026-08-06T23:00:00.000Z"
}

Breaking for admin callers reading expires_at. This is an internal, admin-only surface. The postAdminCreditsHandler JSDoc now states that the grant holds until it is spent, replacing the "including when it expires" line.

Verification

TDD, red before green. Added it("no longer reports an expires_at, because a grant cannot be reduced by a refill") and removed expires_at from the documented-response-shape assertion. Ran it: RED, 2 failed (expected { status: 'success', …(8) } to not have property "expires_at", and the shape test showing + "expires_at": "2026-09-06T23:00:00.000Z"). Implemented, then GREEN.

Check Result
vitest run lib/admins/credits lib/credits 18 files / 128 tests passed
eslint lib/admins lib/credits clean
tsc --noEmit 202 errors, identical to baseline, zero in any file this PR touches
grep -rn getGrantExpiresAt lib app no matches

The remaining 6 tests in postAdminCreditsHandler.test.ts (atomic write pass-through, null previous_credits, 401/403/400 pass-through, 404 unknown account, 500 on write failure, no error-message leak) pass unmodified.

Not yet done: hitting POST /api/admins/credits on the preview to confirm the live response carries 8 fields and no expires_at. Flagging rather than implying it.

Merge order

api#837this PRdocs#300, which removes expires_at from AdminGrantCreditsResponse and rewrites the grant-expiry paragraph. The docs PR is the one surface that must not land before both api PRs are on prod.


Summary by cubic

Removes grant-expiry logic and stops returning expires_at from POST /api/admins/credits, because the monthly refill is now a floor. Previously the endpoint returned expires_at and implied grants expire; now the field is omitted and grants persist until spent.

  • Deletes lib/credits/getGrantExpiresAt.ts and its test; removes its usage and updates postAdminCreditsHandler JSDoc to the present-tense grant contract.
  • Updates tests to assert the response no longer includes expires_at.
  • Merge after api#837 (refill-is-a-floor).
  • Required: admin callers must stop reading expires_at from POST /api/admins/credits.

Written for commit 62fbe8e. Summary will update on new commits.

Review in cubic

getGrantExpiresAt existed only to tell an admin when the monthly reset would
destroy a grant, and carried 20 lines of month-clamping comment explaining
which direction it errs in. Now that the refill is a floor and cannot reduce a
balance (recoupable/chat#1960), the value it computes describes an event that
no longer happens, and leaving it in place tells admins to re-grant credits
they do not need to re-grant.

- Deleted lib/credits/getGrantExpiresAt.ts and its test.
- POST /api/admins/credits no longer returns expires_at. Its JSDoc now says
  the grant holds until it is spent.

Breaking for admin callers reading expires_at, which is an internal, admin-only
surface. Must merge AFTER the refill-is-a-floor fix; until that ships a refill
still reduces balances and a grant genuinely does expire.
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 72a2f578-e999-47bf-9eeb-1a4ee0ba68b9

📥 Commits

Reviewing files that changed from the base of the PR and between 4c69fea and 62fbe8e.

⛔ Files ignored due to path filters (2)
  • lib/admins/credits/__tests__/postAdminCreditsHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/getGrantExpiresAt.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • lib/admins/credits/postAdminCreditsHandler.ts
  • lib/credits/getGrantExpiresAt.ts

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Admin as Admin Client
    participant API as POST /api/admins/credits
    participant Handler as postAdminCreditsHandler
    participant Validate as validateGrantCreditsRequest
    participant Audit as grantCreditsWithAudit
    participant DB as Supabase

    Note over Admin,DB: Admin Grant Credits Flow (current state)

    Admin->>API: POST /api/admins/credits
    API->>Handler: Forward request
    Handler->>Validate: Validate grant payload
    Validate-->>Handler: Validated grant data
    Handler->>Audit: grantCreditsWithAudit(validated)
    Audit->>DB: Atomic write + audit record
    DB-->>Audit: Grant record (created_at, reason, granted_by)
    Audit-->>Handler: Grant result
    Handler-->>API: 200 with grant fields (no expires_at)
    API-->>Admin: {status, grant_id, account_id, remaining_credits, previous_credits, reason, granted_by, granted_at}
    
    Note over Handler,Admin: Admin grant holds until balance is spent<br/>No expiry reported - refill is a floor
Loading

Requires human review: This PR removes the expires_at field from the admin credits API response, a breaking contract change requiring human sign-off for coordination with consumers and the associated docs update.

Re-trigger cubic

@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Aug 18, 2026 12:37am

Request Review

@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Removes expires_at from the admin grant API response — a breaking contract change for callers. The decision also depends on the unverified claim in #837 that the refill is a floor; this diff can't confirm that behavior, so a human should sign off.

Re-trigger cubic

@sweetmantech

Copy link
Copy Markdown
Contributor Author

Preview verification

Tested the PR-head preview (62fbe8e4api-4zmep86hy-recoup.vercel.app, deployment found by sha) with a preview-scoped Privy bearer. The test grant deliberately granted the account's current balance, so previous_credits == remaining_credits and no state changed — no restore cycle needed.

Documented vs actual

# Check (issue Done-when) Expected Actual
1 POST /api/admins/credits no longer returns expires_at 8-field response ['account_id', 'grant_id', 'granted_at', 'granted_by', 'previous_credits', 'reason', 'remaining_credits', 'status'] — exactly 8, expires_at absent
2 No getGrantExpiresAt reference remains grep empty Verified at PR time: grep -rn getGrantExpiresAt lib app → no matches
3 Docs no longer promise an expiry Already live: docs#300 merged 2026-08-17; the published AdminGrantCreditsResponse has no expires_at and the grant description reads "A grant is never reduced by the monthly refill"
4 Error paths intact 404 / 400 / 401 Unknown account → 404; missing reason400; no auth → 401

Prod contrast, and why this should merge promptly

The identical grant against prod (old code) returns 9 fields including expires_at: 2026-09-18T00:38:23.319Z — and since api#837 merged, that date is a promise about an event that can no longer happen: the refill cannot reduce the balance, so nothing expires. Prod's grant response is now actively misleading (it tells admins to re-grant on a schedule they don't need), and the published docs already describe the 8-field shape. This PR closes both gaps.

Ready to merge — the last PR in chat#1960's matrix.

@sweetmantech
sweetmantech merged commit 7a0c590 into main Aug 18, 2026
6 checks passed
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.

1 participant