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:
@@ -22,11 +22,14 @@ from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
match_group_card,
|
||||
pickup_or_upload,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
results_summary,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.DEDUPLICATOR)
|
||||
require_normalization_gate()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -18,8 +18,10 @@ from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
pickup_or_upload,
|
||||
render_hidden_aware_preview,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
from src.core.text_clean import (
|
||||
PRESETS,
|
||||
CleanOptions,
|
||||
@@ -29,6 +31,7 @@ from src.core.text_clean import (
|
||||
)
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.TEXT_CLEANER)
|
||||
require_normalization_gate()
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ if str(_project_root) not in sys.path:
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
pickup_or_upload,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.core.format_standardize import (
|
||||
@@ -25,8 +26,10 @@ from src.core.format_standardize import (
|
||||
StandardizeOptions,
|
||||
standardize_dataframe,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.FORMAT_STANDARDIZER)
|
||||
require_normalization_gate()
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ if str(_project_root) not in sys.path:
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
pickup_or_upload,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.core.missing import (
|
||||
@@ -26,8 +27,10 @@ from src.core.missing import (
|
||||
handle_missing,
|
||||
profile_missing,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.MISSING_HANDLER)
|
||||
require_normalization_gate()
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ if str(_project_root) not in sys.path:
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
pickup_or_upload,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.core.column_mapper import (
|
||||
@@ -27,8 +28,10 @@ from src.core.column_mapper import (
|
||||
infer_mapping,
|
||||
map_columns,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.COLUMN_MAPPER)
|
||||
require_normalization_gate()
|
||||
|
||||
|
||||
|
||||
@@ -11,9 +11,15 @@ _project_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||
if str(_project_root) not in sys.path:
|
||||
sys.path.insert(0, str(_project_root))
|
||||
|
||||
from src.gui.components import hide_streamlit_chrome, require_normalization_gate
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.OUTLIER_DETECTOR)
|
||||
require_normalization_gate()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -11,9 +11,15 @@ _project_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||
if str(_project_root) not in sys.path:
|
||||
sys.path.insert(0, str(_project_root))
|
||||
|
||||
from src.gui.components import hide_streamlit_chrome, require_normalization_gate
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.MULTI_FILE_MERGER)
|
||||
require_normalization_gate()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -11,9 +11,15 @@ _project_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||
if str(_project_root) not in sys.path:
|
||||
sys.path.insert(0, str(_project_root))
|
||||
|
||||
from src.gui.components import hide_streamlit_chrome, require_normalization_gate
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.VALIDATOR_REPORTER)
|
||||
require_normalization_gate()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -17,6 +17,7 @@ if str(_project_root) not in sys.path:
|
||||
from src.gui.components import (
|
||||
hide_streamlit_chrome,
|
||||
pickup_or_upload,
|
||||
require_feature_or_render_upgrade,
|
||||
require_normalization_gate,
|
||||
)
|
||||
from src.core.pipeline import (
|
||||
@@ -28,8 +29,10 @@ from src.core.pipeline import (
|
||||
run_pipeline,
|
||||
validate_pipeline,
|
||||
)
|
||||
from src.license import FeatureFlag
|
||||
|
||||
hide_streamlit_chrome()
|
||||
require_feature_or_render_upgrade(FeatureFlag.PIPELINE_RUNNER)
|
||||
require_normalization_gate()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user