ReviewOS

stacks/analyticshq

The Free limits on the pricing page are enforced nowhere

#50
Open glennmichael123 opened this 9 days ago ·
9 days ago

/pricing sells Free as:

  • 1 site
  • 10k events / month
  • 30-day retention

None of the three is enforced anywhere in the codebase.

  • POST /api/sites (routes/analytics.ts) has no site-count check. A free account can create any number of sites.
  • /collect has bot filtering and GPC handling and no event quota or counter of any kind.
  • Retention is global, not per-plan: scripts/analytics/lib.ts reads ANALYTICSHQ_RETENTION_DAYS (0 = keep forever) and nothing consults the account's plan.

Until recently this was moot, because nothing at all was gated. That changed with the entitlement layer — config/plans.ts now holds limits and planForSite() resolves a plan from the site owner's subscription — so the machinery to enforce these exists and these three simply are not wired into it.

Why it matters now

The pricing page is the argument for upgrading, and right now three quarters of that argument is untrue in the customer's favour. Someone on Free with twelve sites and two million events a month has no reason to ever pay, and we told them the boundary existed.

It is also a slow-acting cost problem: unbounded ingest on a free tier is unbounded storage and query load on a single Postgres box.

Deliberately left out of the entitlement layer

config/plans.ts declares only teammates, and its header says why:

The pricing page also advertises 1 site, 10k events/month and 30-day retention on Free. None of those are enforced anywhere today, and none are listed below. Declaring a limit nothing reads is what plan already was — scaffolding that reads as a feature. Add each one here when the code that enforces it lands, not before.

So the shape of the fix is settled; each limit is a PlanLimits field plus one enforcement point.

Suggested, in order of how cheap and how safe

  1. Site count. One check in POST /api/sites against PLAN_LIMITS[plan].sites. Easiest, and the limit customers notice first. Grandfather existing over-limit accounts — refuse new sites rather than disabling ones people are already using.
  2. Retention. A per-plan floor in the prune job. No hot-path cost, and it caps storage growth on its own.
  3. Event quota. Genuinely the hardest, and worth designing rather than bolting on. /collect is the hot path and must not do a COUNT(*) per beacon, so it needs a counter (a monthly aggregate row, or Redis where configured). Decide up front what happens at the ceiling: dropping events silently corrupts the customer's data, and hard-blocking is a bad first experience. Most competitors keep counting and prompt to upgrade, which is probably right.

Each is independent — worth splitting into three once the approach is agreed.

Sign in to comment on this issue.