feat(license): registration + 1-year licenses + tier scaffolding

A complete offline licensing layer (no internet at any step):

Core
- src/license/ — schema (License, Tier, FeatureFlag), HMAC crypto,
  JSON storage, LicenseManager singleton with activate/renew/
  deactivate/issue_trial. Tier-scaffolded so future SKUs can carve
  per-tool feature sets without consumer-code edits.
- scripts/generate_license.py — creator-only key generator. Mints a
  DTLIC1: blob the buyer pastes into the activation page.

GUI
- New activation form component (src/gui/components/activation.py).
- hide_streamlit_chrome() now inline-renders the activation form when
  no valid license is present (every page short-circuits to the form
  until activated).
- Sidebar shows tier + days remaining; renewal warning under 30 days.
- New pages/_Activate.py for revisiting the form after activation.

CLI
- src/license_cli.py — activate / renew / status / trial / deactivate
  commands. Exempt from the guard.
- src/cli_license_guard.py — drop-in guard call added to every tool
  CLI's main(). Lets --help through; respects DATATOOLS_DEV_MODE.

i18n
- New activation.* and license.* keys in en.json + es.json
  (page title, form labels, status badges, renewal warnings, error
  messages). Pack parity test stays green.

Test infrastructure
- tests/conftest.py autouse fixture sets DATATOOLS_DEV_MODE=1 so the
  existing 1916 tests continue to pass.
- isolated_license_path / activated_license_manager /
  unactivated_license_manager fixtures for tests that want to drive
  the real check.

Tests (+79)
- tests/test_license.py (40): schema, crypto roundtrip, blob
  encode/decode, tier→feature mapping, activation flow, name/email
  mismatch rejection, tamper detection, expiration, renewal,
  dev-mode bypass.
- tests/test_license_cli.py (26): every license_cli command +
  subprocess tests confirming every tool CLI refuses to run without
  a license, --help always works, DEV_MODE bypasses.
- tests/gui/test_activation.py (13): gate blocks without license,
  passes with trial, activation form submission unlocks the gate,
  sidebar status, renewal warning, i18n.

Total: 1916 → 1995 tests. All pass under the strict warning filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 16:54:23 +00:00
parent b2c7b94fe9
commit e435103113
27 changed files with 2798 additions and 6 deletions

View File

@@ -56,6 +56,44 @@
"body": "Clicking the button below will terminate the DataTools server. Any unsaved work in other tools will be lost. Once the app shuts down you can close this window.",
"button": "Close the app"
},
"activation": {
"page_title": "DataTools — Activate",
"title": "🔑 Activate DataTools",
"intro": "DataTools needs to be activated before any tools unlock. Enter the name and email tied to your purchase, then paste the license blob from your delivery email.",
"name_label": "Full name",
"name_help": "Must match the name on your purchase receipt.",
"email_label": "Email",
"email_help": "Must match the email on your purchase receipt.",
"blob_label": "License blob",
"blob_help": "Begins with `DTLIC1:` — paste the entire string.",
"activate_button": "Activate",
"renew_button": "Apply renewal",
"trial_button": "Start 1-year trial",
"trial_help": "Skips the paid blob and self-issues a 1-year license tied to your name and email. Useful for evaluating before purchase.",
"or_separator": "— or —",
"success": "Activated! Welcome, {name}. Your license is valid until {expires}.",
"renewed": "License renewed. New expiry: {expires}.",
"errors_heading": "Activation problem",
"deactivate_button": "Deactivate this device",
"deactivate_help": "Removes the local license file. You'll need to re-paste your blob to reactivate."
},
"license": {
"status_active": "{tier} · {days} days left",
"status_trial": "Trial · {days} days left",
"status_expired": "Expired",
"status_not_activated": "Not activated",
"status_invalid": "License invalid",
"renewal_warning_30": "⚠️ License expires in {days} days. Renew soon to avoid interruption.",
"renewal_warning_expired": "🛑 License expired on {date}. Renew to continue using DataTools.",
"tier_trial": "Trial",
"tier_core": "Core",
"tier_pro": "Pro",
"tier_enterprise": "Enterprise",
"registered_to": "Registered to {name} · {email}",
"expires_on": "Expires on {date}",
"issued_on": "Issued on {date}",
"view_details": "License details"
},
"tools": {
"01_deduplicator": {
"name": "Deduplicator",