feat: add generalised tier system with BYOK and local vision unlocks

This commit is contained in:
pyr0ball 2026-03-25 11:04:55 -07:00
parent 76506a390e
commit 97ee2c20b6
3 changed files with 125 additions and 0 deletions

View file

@ -0,0 +1,3 @@
from .tiers import can_use, tier_label, TIERS, BYOK_UNLOCKABLE, LOCAL_VISION_UNLOCKABLE
__all__ = ["can_use", "tier_label", "TIERS", "BYOK_UNLOCKABLE", "LOCAL_VISION_UNLOCKABLE"]

View file

@ -0,0 +1,76 @@
"""
Tier system for CircuitForge products.
Tiers: free < paid < premium < ultra
Products register their own FEATURES dict and pass it to can_use().
BYOK_UNLOCKABLE: features that unlock when the user has any configured
LLM backend (local or API key). These are gated only because CF would
otherwise provide the compute.
LOCAL_VISION_UNLOCKABLE: features that unlock when the user has a local
vision model configured (e.g. moondream2). Distinct from BYOK a text
LLM key does NOT unlock vision features.
"""
from __future__ import annotations
TIERS: list[str] = ["free", "paid", "premium", "ultra"]
# Features that unlock when the user has any LLM backend configured.
# Each product extends this frozenset with its own BYOK-unlockable features.
BYOK_UNLOCKABLE: frozenset[str] = frozenset()
# Features that unlock when the user has a local vision model configured.
LOCAL_VISION_UNLOCKABLE: frozenset[str] = frozenset()
def can_use(
feature: str,
tier: str,
has_byok: bool = False,
has_local_vision: bool = False,
_features: dict[str, str] | None = None,
) -> bool:
"""
Return True if the given tier (and optional unlocks) can access feature.
Args:
feature: Feature key string.
tier: User's current tier ("free", "paid", "premium", "ultra").
has_byok: True if user has a configured LLM backend.
has_local_vision: True if user has a local vision model configured.
_features: Featuremin_tier map. Products pass their own dict here.
If None, all features are free.
"""
features = _features or {}
if feature not in features:
return True
if has_byok and feature in BYOK_UNLOCKABLE:
return True
if has_local_vision and feature in LOCAL_VISION_UNLOCKABLE:
return True
min_tier = features[feature]
try:
return TIERS.index(tier) >= TIERS.index(min_tier)
except ValueError:
return False
def tier_label(
feature: str,
has_byok: bool = False,
has_local_vision: bool = False,
_features: dict[str, str] | None = None,
) -> str:
"""Return a human-readable label for the minimum tier needed for feature."""
features = _features or {}
if feature not in features:
return "free"
if has_byok and feature in BYOK_UNLOCKABLE:
return "free (BYOK)"
if has_local_vision and feature in LOCAL_VISION_UNLOCKABLE:
return "free (local vision)"
return features[feature]

46
tests/test_tiers.py Normal file
View file

@ -0,0 +1,46 @@
import pytest
from circuitforge_core.tiers import can_use, TIERS, BYOK_UNLOCKABLE, LOCAL_VISION_UNLOCKABLE
def test_tiers_order():
assert TIERS == ["free", "paid", "premium", "ultra"]
def test_free_feature_always_accessible():
# Features not in FEATURES dict are free for everyone
assert can_use("nonexistent_feature", tier="free") is True
def test_paid_feature_blocked_for_free_tier():
# Caller must register features — test via can_use with explicit min_tier
assert can_use("test_paid", tier="free", _features={"test_paid": "paid"}) is False
def test_paid_feature_accessible_for_paid_tier():
assert can_use("test_paid", tier="paid", _features={"test_paid": "paid"}) is True
def test_premium_feature_accessible_for_ultra_tier():
assert can_use("test_premium", tier="ultra", _features={"test_premium": "premium"}) is True
def test_byok_unlocks_byok_feature():
byok_feature = next(iter(BYOK_UNLOCKABLE)) if BYOK_UNLOCKABLE else None
if byok_feature:
assert can_use(byok_feature, tier="free", has_byok=True) is True
def test_byok_does_not_unlock_non_byok_feature():
assert can_use("test_paid", tier="free", has_byok=True,
_features={"test_paid": "paid"}) is False
def test_local_vision_unlocks_vision_feature():
vision_feature = next(iter(LOCAL_VISION_UNLOCKABLE)) if LOCAL_VISION_UNLOCKABLE else None
if vision_feature:
assert can_use(vision_feature, tier="free", has_local_vision=True) is True
def test_local_vision_does_not_unlock_non_vision_feature():
assert can_use("test_paid", tier="free", has_local_vision=True,
_features={"test_paid": "paid"}) is False