docs(license): document activation flow, tier system, dev bypass
- USER-GUIDE EN + ES gain a §0 "First launch — activation" section covering paid blob activation, 1-year trial, renewal, file location, and device-swap. - REQUIREMENTS §17a "Licensing" — storage path, activation model, lifetime, tier list, dev bypass env var. Test count: 1995. - DEVELOPER gains a "Licensing" recipe in the Extension recipes section: public API, feature-flag add, tier add, minting via the creator-only script. - DECISIONS §9b — log the offline-HMAC choice with the threat-model trade-off (motivated piracy not stopped; honor-system + 30-day refund covers casual sharing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,61 @@ st.warning(t("gate.warning", name=filename)) # {name} interpolated via str.for
|
||||
- Do **not** put strings inside the farewell-overlay JS payload without going through `_js_html_safe()` in `src/gui/components/_legacy.py`; the helper escapes both the JS string terminator and HTML special chars. The test `TestFarewellEscape` pins that contract.
|
||||
- The sidebar picker is mounted by `hide_streamlit_chrome()`, so every page that calls that helper automatically gets the picker. Pages that don't call it (rare) can call `render_language_selector()` directly.
|
||||
|
||||
### Licensing
|
||||
|
||||
The license layer lives at ``src/license/``. The public API:
|
||||
|
||||
```python
|
||||
from src.license import (
|
||||
get_manager, require_feature, current_state,
|
||||
FeatureFlag, Tier, License,
|
||||
)
|
||||
|
||||
mgr = get_manager()
|
||||
if not mgr.is_valid():
|
||||
raise RuntimeError("Not licensed")
|
||||
require_feature(FeatureFlag.DEDUPLICATOR)
|
||||
```
|
||||
|
||||
**Storage**: ``~/.datatools/license.json`` (override via
|
||||
``DATATOOLS_LICENSE_PATH``). Signed locally with HMAC-SHA256 using a
|
||||
secret read from ``DATATOOLS_LICENSE_SECRET`` (build-time replace; the
|
||||
in-repo default is a development placeholder).
|
||||
|
||||
**Dev bypass**: ``DATATOOLS_DEV_MODE=1`` short-circuits every check.
|
||||
The test suite's autouse fixture sets this so existing tests don't
|
||||
need their own license fixtures. Tests that need the real check
|
||||
explicitly use ``isolated_license_path`` /
|
||||
``activated_license_manager`` / ``unactivated_license_manager``.
|
||||
|
||||
**Adding a feature flag**:
|
||||
|
||||
1. Add the enum value to ``FeatureFlag`` in ``src/license/schema.py``.
|
||||
2. Add it to the relevant tier's set in
|
||||
``FEATURES_BY_TIER`` in ``src/license/features.py``.
|
||||
3. Gate at the call site: ``require_feature(FeatureFlag.YOUR_FLAG)``.
|
||||
|
||||
**Adding a new tier**:
|
||||
|
||||
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.
|
||||
|
||||
**Minting a license** (creator-only):
|
||||
|
||||
```bash
|
||||
DATATOOLS_LICENSE_SECRET=<shipping-secret> \
|
||||
python scripts/generate_license.py \
|
||||
--name "Jane Doe" --email jane@example.com \
|
||||
--tier core --years 1
|
||||
```
|
||||
|
||||
The script prints a ``DTLIC1:`` blob to stdout — deliver this in the
|
||||
Gumroad / purchase email. The buyer pastes it into the activation
|
||||
page or runs ``python -m src.license_cli activate <blob> --name ...``.
|
||||
|
||||
### Add a format-standardizer field type
|
||||
|
||||
1. Add value to `FieldType` enum in `core/format_standardize.py`.
|
||||
|
||||
Reference in New Issue
Block a user