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:
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user