Skip to content

feat(gating): free vs Pro feature gating + Anthropic migration#24

Merged
PAMulligan merged 2 commits into
8-add-client-side-entitlement-layer-and-feature-flag-systemfrom
9-define-and-implement-free-vs-pro-feature-gating
Jul 21, 2026
Merged

feat(gating): free vs Pro feature gating + Anthropic migration#24
PAMulligan merged 2 commits into
8-add-client-side-entitlement-layer-and-feature-flag-systemfrom
9-define-and-implement-free-vs-pro-feature-gating

Conversation

@PAMulligan

Copy link
Copy Markdown
Contributor

Closes #9. Stacked on #8 (PR #23) — base is the #8 branch, so this diff shows only #9's work. Rebase onto main once #8 merges.

Per the issue decision, this PR takes the full-build path: it wires every gate to the entitlement layer, builds the hosted free-tier AI proxy client, adds a server-metered free quota, and migrates the AI provider OpenAI → Anthropic.

Free/Pro matrix

Documented in docs/free-vs-pro-gating.md. Every gate reads an entitlement-store flag (isPro/canUse*), never an ad-hoc apiKey check.

Feature Free Pro Gate
SEO scoring + score breakdown ✅ Full ✅ Full ungated — never gate
AI recommendations ✅ Capped monthly via hosted proxy ✅ Higher cap, or unlimited with own key useCanUseAI / useAiStatus
Advanced analysis canUseAdvancedOptions
Multi-language output ❌ (English) canUseMultiLanguage
Schema generation canUseSchema
Bring your own Anthropic key canBringOwnKey

Three AI paths

useAiStatus() collapses provider + entitlement into one mode; ai.ts routes accordingly:

  • byok — Pro + own Anthropic key → browser→Anthropic directly (anthropic.ts, claude-opus-4-8), unlimited/unmetered.
  • pro — Pro, no key → hosted proxy POST /ai/generate with X-Optia-Entitlement.
  • free — not Pro → hosted proxy metered by anonymous install id.
  • locked — quota exhausted with no other path → AI controls disable + friendly upsell.

Invariants enforced: a stored key only unlocks the direct path when isPro (a free user can never self-serve uncapped AI); the BYO key never transits Optia's backend; the server is the quota authority (client caches quota.remaining, never mints it).

Provider migration (OpenAI → Anthropic)

openai.tsanthropic.ts on @anthropic-ai/sdk; new ai-proxy.ts (typed errors) and ai.ts facade; storage key openai_api_keyanthropic_api_key; dev CORS proxy and all UI copy updated to Anthropic.

Notable decisions

  • Quota is monthly, not daily. The shipped backend (optia-backend /ai/generate) meters a monthly free allowance; the client follows the backend rather than the issue's proposed daily cap.
  • Free users generate on demand. Auto-generate-on-analyze runs only on the uncapped byok/pro paths, so a single analysis doesn't drain a free user's monthly allowance.
  • Advanced context applies only on the BYO-key path — the proxy accepts only {checkId, keyword, context}.
  • Model: BYO-key path uses claude-opus-4-8 (Anthropic default). Change AI_MODEL in anthropic.ts for a cheaper tier.

Review

An adversarial review (13 agents: 4 dimension-finders + per-finding skeptics) confirmed 8 findings; all are fixed in this PR:

  • Server quota_exceeded now drives cached remaining to 0 so the gate converges to the upsell (no error-toast loop).
  • Fixed a dev-proxy /v1/v1/ double-prefix that broke BYO-key in the browser preview.
  • Pro request without a token is now recorded against the subject actually metered (free), not Pro.
  • Parallel generate-all quota writes reconcile conservatively (lowest remaining per period).
  • Locked-state message is tier-aware (a Pro user isn't told to "upgrade to Pro").
  • All stale "OpenAI" UI copy → Anthropic, and the gate hint is no longer a dead end.

Acceptance criteria

  • Final free/Pro matrix documented (docs/free-vs-pro-gating.md)
  • SEO scoring + score breakdown remain fully free (regression test)
  • AI recommendations gated by canUseAI / aiQuotaRemaining from the entitlement layer
  • Advanced options, schema generation, and BYO-key gated to Pro
  • Every gate reads the entitlement layer, never ad-hoc checks
  • Graceful, friendly upsell messaging when a free user hits a gate (no dead ends)
  • Tests cover free and Pro rendering of each gated surface
  • CI passes (locally: 411 tests, tsc + vite build, eslint clean)

Test plan

  • pnpm test — 411 passing across 24 files, incl. the proxy client, the Anthropic direct client, the AI router (all three paths + locked + quota-exhaustion lock + metered-subject), the quota model, and free/Pro rendering of every gated surface.
  • pnpm build (tsc + vite) and pnpm lint — clean.
  • Manual smoke: free user gets scoring + a monthly AI allowance and sees Pro pills on gated surfaces; activate a license → key field, multi-language, schema, and advanced options unlock; exhaust the free allowance → controls lock with an upsell.

🤖 Generated with Claude Code

PAMulligan and others added 2 commits July 21, 2026 11:38
…ent-layer-and-feature-flag-system

feat(entitlement): add client-side entitlement layer and feature-flag system
Implements the free/Pro split (closes #9), stacked on the #8 entitlement
layer. AI provider migrated OpenAI -> Anthropic as part of the same change.

Free/Pro matrix (docs/free-vs-pro-gating.md):
- SEO scoring + score breakdown: fully free, never gated.
- AI recommendations: free via a capped, server-metered hosted proxy; Pro
  gets a higher proxy cap or unlimited with their own key.
- Advanced options, multi-language output, schema generation, and BYO key:
  Pro only. Every gate reads an entitlement-store flag (canUse*/isPro),
  never an ad-hoc apiKey check.

Three AI paths (app/src/lib/ai.ts + entitlement-store useAiStatus):
- byok: Pro + own Anthropic key -> browser->Anthropic directly, unlimited.
- pro:  Pro, no key -> hosted proxy authenticated by X-Optia-Entitlement.
- free: not Pro -> hosted proxy metered by install id.
A stored key only unlocks the direct path when isPro, so a free user can
never self-serve uncapped AI. The BYO key never transits Optia's backend.

Provider migration:
- openai.ts -> anthropic.ts using @anthropic-ai/sdk (claude-opus-4-8,
  dangerouslyAllowBrowser); new ai-proxy.ts for POST /ai/generate; ai.ts
  facade routes + records server-authoritative quota. Storage key
  openai_api_key -> anthropic_api_key; dev CORS proxy and all UI labels
  updated to Anthropic.

Quota is server-authoritative: /ai/generate returns quota {limit,remaining,
period} which the client caches; on a server quota_exceeded the client
drives remaining to 0 so the gate converges to a friendly upsell (no dead
ends). Free users generate on demand (auto-generate only runs on the
uncapped byok/pro paths) to conserve the monthly allowance.

Note: the shipped backend meters a MONTHLY free allowance (not the daily
cap the issue proposed); the client follows the backend.

Tests: 411 passing. New coverage for the proxy client, the Anthropic
direct client, the AI router (all three paths + locked + quota-exhaustion
lock + metered-subject), the quota model, and free/Pro rendering of every
gated surface (schema, multi-language, BYO-key, advanced options, and the
scoring-stays-free guarantee). tsc + vite build + eslint all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PAMulligan
PAMulligan merged commit e9c737d into 8-add-client-side-entitlement-layer-and-feature-flag-system Jul 21, 2026
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