Skip to content

plans: shared Rank() helper for tier ordering#10

Merged
mastermanas805 merged 1 commit into
masterfrom
chore/unified-tier-rank-fresh
May 13, 2026
Merged

plans: shared Rank() helper for tier ordering#10
mastermanas805 merged 1 commit into
masterfrom
chore/unified-tier-rank-fresh

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

  • Add plans.Rank(tier string) int — single canonical tier ordering, shared across api/, worker/, and any future module.
  • Canonical mapping: anonymous=0, free=1, hobby=2, growth=3, pro=4, team=5. Unknown tiers return -1 so callers can short-circuit "no transition direction" rather than guess.
  • Case- and whitespace-insensitive. Yearly variants intentionally NOT auto-normalised — callers pass them through CanonicalTier first if they want "pro_yearly" to rank the same as "pro" (billing.go does exactly that pattern).

Why

Two package-private rank functions used to live in the api repo with subtly different orderings:

  • internal/handlers/billing.go::tierRank — 6 tiers (anonymous .. team).
  • internal/handlers/admin_customers.go::adminTierRank — 4 tiers (free .. team), off-by-one against billing for the same names.

The discrepancy never bit production because the admin surface never sees anonymous / growth, but it's a footgun the moment the admin surface widens. Promoting a single canonical function eliminates the drift risk.

The follow-up api PR (handlers: use plans.Rank + consolidate agent_action constants) deletes both local copies and routes callers through plans.Rank. It depends on this PR landing first.

Test plan

  • go test ./plans/... green (3 new tests: TestRank_AllStandardTiers, TestRank_UnknownReturnsMinusOne, TestRank_MonotonicallyIncreasing, plus TestRank_CaseInsensitive)
  • go test ./... green across the common module

Generated with Claude Code

Two package-private rank functions used to live in the api repo
(internal/handlers/billing.go::tierRank and
internal/handlers/admin_customers.go::adminTierRank). They had subtly
different orderings — billing.go covered 6 tiers (anonymous .. team),
admin_customers.go covered 4 (free .. team) and was off-by-one against
billing for the same names. The discrepancy never bit production because
the admin surface never sees anonymous/growth, but it's a footgun.

Promote a single canonical ordering here so all modules share one rank
function. Returns -1 for unknown tiers; callers must guard against the
sentinel when comparing ranks (a negative rank means "no transition
direction"). Yearly variants are NOT auto-normalised — callers pass them
through CanonicalTier first if they want "pro_yearly" to rank as "pro".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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