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

@@ -176,6 +176,8 @@ $49-79/bundle · $149 full suite (when 3+ exist).
| May 1 (v1.6) | Add `src/core/errors.py` structured hierarchy | Uniform helpful messages across CLI + GUI. See TECHNICAL §7. |
| May 13 (v1.6) | Ship in-house JSON i18n + EN/ES packs | Expand addressable market (Spanish-first buyers, LatAm bookkeepers) without a `gettext` build step. JSON packs editable by non-devs; parity test prevents drift. See TECHNICAL §10b. |
| May 13 (v1.6) | Ship licensing: 1-year HMAC-signed blobs, name+email registration, offline verification, tier-scaffolded for future SKUs | Unlock the lifetime-update business model without recurring infra. Honor-system DRM (HMAC + 30-day refund) — sufficient at $49. See §9b below. |
| May 13 (v1.6) | Add Lite SKU (Dedup + Text Cleaner + Format Standardizer) | Lower-priced entry point for buyers who only need the three universal tools. Per-tool feature gating + lock badges on the home grid surface the upgrade path. See §9b. |
| May 13 (v1.6) | Remove user-facing free trial | A 1-year all-features trial undercut the paid Lite SKU. Paid-only keeps tier economics clean. Internal ``_mint`` API still exists for tests and the seller's key generator. See §9b. |
## 9b. Licensing model
@@ -203,6 +205,18 @@ $49-79/bundle · $149 full suite (when 3+ exist).
**Future SKUs**: the ``FEATURES_BY_TIER`` table in ``src/license/features.py`` is the single source of truth for "which tools each tier unlocks". Adding a PRO SKU that excludes the pipeline runner is a 1-line edit there + a 1-line edit at the gate site. No consumer-code churn.
**v1.6 SKU lineup**:
| Tier | Tools unlocked | Notes |
|---|---|---|
| LITE | Deduplicator, Text Cleaner, Format Standardizer | Entry SKU. Three universal tools that handle the most common bookkeeping / RevOps / Klaviyo prep workflows. |
| CORE | All 9 tools | Full v1 suite. |
| PRO | All 9 tools (scaffolded) | Reserved for future per-feature carve-outs (e.g., scheduled pipelines, API access). |
| ENTERPRISE | All 9 tools (scaffolded) | Reserved for future bulk / multi-seat SKUs. |
| TRIAL | Same as LITE | Deprecated — no longer issuable. Mapping kept for any legacy on-disk trial licenses to load without error. |
**Trial removed (v1.6)**: a 1-year free trial that unlocked every tool would undercut the paid Lite SKU (why pay for Lite when trial gives more for longer?). Paid-only keeps the funnel clean. The internal ``LicenseManager._mint`` API still exists for tests and for the seller's ``scripts/generate_license.py`` key generator; there's no user-facing way to self-issue a license.
## 8. Re-lock triggers
These criteria are load-bearing. Triggers for explicit re-evaluation:

View File

@@ -165,8 +165,37 @@ explicitly use ``isolated_license_path`` /
1. Add the enum value to ``Tier``.
2. Add a row to ``FEATURES_BY_TIER`` listing the unlocked flags.
3. Add ``license.tier_<name>`` translation keys to every i18n pack.
4. The activation flow, sidebar status badge, and feature gate all
pick up the new tier automatically.
4. The activation flow, sidebar status badge, feature gate, and home
grid lock badge all pick up the new tier automatically.
**Worked example — the Lite tier**:
```python
# src/license/schema.py
class Tier(str, Enum):
LITE = "lite" # new
CORE = "core"
...
# src/license/features.py
FEATURES_BY_TIER = {
...
Tier.LITE: frozenset({
FeatureFlag.DEDUPLICATOR,
FeatureFlag.TEXT_CLEANER,
FeatureFlag.FORMAT_STANDARDIZER,
}),
Tier.CORE: _all(),
...
}
```
Then in en.json/es.json add ``license.tier_lite``. That's it — the
existing ``require_feature_or_render_upgrade`` (GUI) and
``guard(feature=...)`` (CLI) calls in every tool page/CLI route a
Lite user into the upgrade prompt for any tool the tier doesn't
unlock. The home grid's lock badge fires off the same feature
lookup.
**Minting a license** (creator-only):

View File

@@ -174,13 +174,14 @@ and proceeds.
- **Dev**: pytest, tox.
## 16. Test coverage
- 1,995 tests passing, 0 skipped, 0 xfailed.
- 1,843 core + CLI tests (run with `pytest -m 'not gui'` for a quick loop).
Includes 40 license-layer unit tests + 26 license-CLI tests.
- 152 GUI tests under `tests/gui/` driving Streamlit pages via `AppTest`
- 2,024 tests passing, 0 skipped, 0 xfailed.
- 1,859 core + CLI tests (run with `pytest -m 'not gui'` for a quick loop).
Includes 40 license-layer unit tests, 25 license-CLI tests, and
17 Lite-tier feature-map + guard tests.
- 165 GUI tests under `tests/gui/` driving Streamlit pages via `AppTest`
(smoke + EN/ES localization, chrome, gate, workflows, dedup review,
advanced panels, error paths, findings panel, activation + license
gate). Marked `gui`.
advanced panels, error paths, findings panel, activation +
license gate, Lite-tier per-page lock behaviour). Marked `gui`.
- Includes 15 perf-shape regression tests.
- Fixture corpora: text-cleaner (21), encodings (31), reference UTF-8 (9), format-cleaner (199 buyer cases + 20-row international stress fixture), missing-handler (3 use cases + 16 edge cases), column-mapper (3 use cases + 5 edge cases).
- Run: `python run_tests.py [--tool …] [--fixtures] [--coverage]`.
@@ -199,15 +200,31 @@ and proceeds.
(``DTLIC1:...``) on first launch; app verifies the signature
offline + matches the buyer-entered name/email to the embedded
values.
- **No free trial**: every license requires a paid blob from the
seller. The user-facing trial flow (button + ``license_cli trial``
subcommand) was removed in v1.6 to keep paid-tier economics clean.
- **Lifetime**: every license is 1 year by default. Renewal applies a
fresh blob without losing the embedded buyer identity.
- **Tiers**: ``trial``, ``core`` (the v1 SKU — all 9 tools), ``pro``,
``enterprise``. PRO and ENTERPRISE are scaffolded for future SKUs;
they currently unlock the same feature set as CORE.
fresh blob without losing the embedded buyer identity. Tier may
change during renewal (Lite → Core upgrade path).
- **Tiers**:
- ``lite`` — Deduplicator + Text Cleaner + Format Standardizer.
Buyer pays once, gets the three universally-useful tools.
- ``core`` — every Ready tool (all 9 in v1.6).
- ``pro``, ``enterprise`` — scaffolded for future SKUs; currently
mirror Core. Add per-SKU restrictions by editing
``FEATURES_BY_TIER`` in ``src/license/features.py``.
- ``trial`` — kept in the enum for backwards compat with any
field-tested trial licenses but no longer issuable.
- **Feature flags**: every tool has a stable feature id matching its
``tool_id`` in :mod:`src.gui.tools_registry`. Adding a future per-
tool SKU is a one-line change to ``FEATURES_BY_TIER`` — no consumer
code edits.
- **Per-tool gating**: each tool page (GUI) and tool CLI calls
``require_feature(FeatureFlag.<TOOL>)`` at entry. GUI shows an
upgrade prompt + button to the Activate page; CLI prints a
message naming the locked feature and exits with code 2.
- **Lock badge**: the home grid shows a red 🔒 Locked pill on tool
cards the current tier doesn't unlock.
- **Dev bypass**: ``DATATOOLS_DEV_MODE=1`` skips every check (used by
the test suite and during development).
- **No internet**: signature verification is fully offline. The

View File

@@ -8,16 +8,20 @@
DataTools debe activarse antes de desbloquear cualquier herramienta. En el primer arranque verás la pantalla **Activar**.
| Si tienes… | Haz esto |
Introduce tu nombre completo y correo, pega el código de licencia del correo de compra (empieza con `DTLIC1:`) y pulsa **Activar**. La renovación funciona igual: pega el código de renovación y pulsa **Aplicar renovación**.
**Niveles**:
| Nivel | Herramientas |
|---|---|
| Un código de licencia de pago (del correo de compra) | Introduce tu nombre completo y correo, pega el código completo (empieza con `DTLIC1:`), pulsa **Activar**. |
| Nada todavía, quieres evaluar | Introduce nombre y correo, pulsa **Iniciar prueba de 1 año**. La app emite localmente una licencia de prueba de 1 año — sin pago. |
| **Lite** | Eliminador de duplicados · Limpiador de texto · Estandarizador de formatos |
| **Core** | Las 9 herramientas |
La renovación funciona igual: pega el código de renovación, pulsa **Aplicar renovación**. La fecha de caducidad se reinicia a un año desde la renovación.
Un usuario Lite que abra una herramienta exclusiva de Core verá un mensaje "Actualiza tu licencia". La página de inicio también muestra una marca 🔒 Bloqueado en las tarjetas de las herramientas que tu nivel no incluye. Para actualizar, pega un código Core en la página Activar.
El archivo de licencia vive en `~/.datatools/license.json` (Windows: `C:\Users\<tú>\.datatools\license.json`). La barra lateral muestra tu nivel y los días restantes en todo momento. Aparece un aviso de renovación 30 días antes de la caducidad.
Cada licencia dura 1 año. La barra lateral muestra tu nivel y los días restantes en todo momento; aparece un aviso de renovación 30 días antes de la caducidad. El archivo de licencia vive en `~/.datatools/license.json` (Windows: `C:\Users\<tú>\.datatools\license.json`).
Si quieres usar la misma licencia en otro equipo, desactiva éste (página Activar → **Desactivar este dispositivo**) y vuelve a pegar tu código en el nuevo.
Para usar la misma licencia en otro equipo: desactiva éste (página Activar → **Desactivar este dispositivo**) y vuelve a pegar tu código en el nuevo.
## 1. Instalación

View File

@@ -8,16 +8,20 @@
DataTools must be activated before any tools unlock. On first launch you'll see the **Activate** screen.
| You have… | Do this |
Enter your full name + email, paste the license blob from your purchase email (starts with `DTLIC1:`), and click **Activate**. Renewal works the same way — paste the renewal blob, click **Apply renewal**.
**Tiers**:
| Tier | Tools |
|---|---|
| A paid license blob (from your purchase email) | Enter your full name + email, paste the entire blob (starts with `DTLIC1:`), click **Activate**. |
| Nothing yet, want to evaluate | Enter your name + email, click **Start 1-year trial**. The app self-issues a 1-year trial license — no payment required. |
| **Lite** | Deduplicator · Text Cleaner · Format Standardizer |
| **Core** | All 9 tools |
Renewal works the same way: paste the renewal blob, click **Apply renewal**. The expiry resets to one year from the renewal date.
A Lite user opening a Core-only tool sees an "Upgrade your license" prompt. The home page also shows a 🔒 Locked badge on tool cards your tier doesn't unlock. To upgrade, paste a Core blob on the Activate page.
The license file lives at `~/.datatools/license.json` (Windows: `C:\Users\<you>\.datatools\license.json`). The sidebar shows your tier and days remaining at all times. A renewal warning appears 30 days before expiry.
Every license lasts 1 year. The sidebar shows your tier and days remaining at all times; a renewal warning appears 30 days before expiry. The license file lives at `~/.datatools/license.json` (Windows: `C:\Users\<you>\.datatools\license.json`).
If you ever want to use the same license on a different machine, deactivate this one (Activate page → **Deactivate this device**) and re-paste your blob on the new machine.
To use the same license on a different machine: deactivate this one (Activate page → **Deactivate this device**) and re-paste your blob on the new machine.
## 1. Install