feat(license): add Lite SKU; remove user-facing free trial

Two coupled changes:

1. Lite tier
   - New Tier.LITE in src/license/schema.py.
   - FEATURES_BY_TIER[Tier.LITE] = {Deduplicator, Text Cleaner,
     Format Standardizer}. The three universally-useful tools that
     cover the most common bookkeeping / RevOps / Klaviyo prep
     workflows. Other six tools require Core.
   - i18n: license.tier_lite, license.feature_locked_title,
     license.feature_locked_body, license.upgrade_link,
     license.status_locked (en + es).
   - Per-tool feature gate at every GUI tool page
     (require_feature_or_render_upgrade) and every tool CLI
     (guard(feature=...)). A locked tool renders an upgrade
     prompt + Manage-license button (GUI) or exits with code 2
     (CLI).
   - Home grid: tool cards the user's tier doesn't unlock get a
     red 🔒 Locked badge in place of green Ready.

2. Trial removed
   - Activation form's "Start 1-year trial" button removed.
   - license_cli's `trial` subcommand removed.
   - activation.trial_button / activation.trial_help i18n keys
     dropped (pack parity test stays green).
   - Tier.TRIAL stays in the enum (back-compat with any field-
     tested trial licenses); LicenseManager._mint stays internal
     for tests and the seller's key generator.
   - Decision logged in DECISIONS §9b: a 1-year all-features
     trial undercuts paid Lite; paid-only keeps tier economics
     clean.

Tests (+29 net): +17 Lite-tier unit/guard tests + 13 Lite-tier
GUI tests + 1 trial-absent assertion - 2 trial CLI tests - 1
trial GUI button test. Total: 1995 → 2024.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 17:19:30 +00:00
parent e612c751a8
commit d32b58e61a
33 changed files with 621 additions and 153 deletions

View File

@@ -29,7 +29,26 @@ def _all() -> FrozenSet[FeatureFlag]:
FEATURES_BY_TIER: dict[Tier, FrozenSet[FeatureFlag]] = {
Tier.TRIAL: _all(),
# TRIAL is no longer reachable from the activation UI (no free
# trial in v1.6) but we leave the mapping in place so legacy
# trial licenses on disk still load. Effectively LITE-equivalent
# if anyone has one.
Tier.TRIAL: frozenset({
FeatureFlag.DEDUPLICATOR,
FeatureFlag.TEXT_CLEANER,
FeatureFlag.FORMAT_STANDARDIZER,
}),
# LITE — the cheap SKU. Three tools that cover the common
# bookkeeping / RevOps workflow: clean text, standardise formats,
# remove duplicates. Buyers who want missing-handler, column-
# mapper, outlier-detector, validator, or the pipeline runner
# upgrade to CORE.
Tier.LITE: frozenset({
FeatureFlag.DEDUPLICATOR,
FeatureFlag.TEXT_CLEANER,
FeatureFlag.FORMAT_STANDARDIZER,
}),
# CORE — the v1 full SKU. Every Ready tool.
Tier.CORE: _all(),
# Pre-wired for future SKUs. Today they mirror CORE so the gating
# tests exercise the lookup path without making a marketing claim.

View File

@@ -31,12 +31,19 @@ from typing import Any
class Tier(str, Enum):
"""License tier. Drives the feature set the active license unlocks.
Order matters: TRIAL < CORE < PRO < ENTERPRISE. A higher tier
inherits every feature of the lower tiers — see
Order matters for upgrade pricing: LITE < CORE < PRO < ENTERPRISE.
Each higher tier inherits every feature of the lower tiers — see
:data:`.features.FEATURES_BY_TIER`.
``TRIAL`` is retained for backward-compat with the brief trial-
enabled build, but is no longer reachable from any user-facing
entry point (the GUI trial button and the ``license_cli trial``
subcommand were both removed in v1.6). The enum value stays so
a buyer with a trial license on disk doesn't see a load failure.
"""
TRIAL = "trial"
LITE = "lite"
CORE = "core"
PRO = "pro"
ENTERPRISE = "enterprise"