syntaxmatrix-core 1.0.8__tar.gz → 1.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {syntaxmatrix_core-1.0.8/syntaxmatrix_core.egg-info → syntaxmatrix_core-1.0.9}/PKG-INFO +1 -1
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/setup.py +1 -1
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/agents_orchestrer.py +5 -4
- syntaxmatrix_core-1.0.9/syntaxmatrix/premium/__init__.py +18 -0
- syntaxmatrix_core-1.0.9/syntaxmatrix/premium/catalogue/__init__.py +267 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/premium/gate.py +23 -39
- syntaxmatrix_core-1.0.9/syntaxmatrix/premium/runtime_paths.py +109 -0
- syntaxmatrix_core-1.0.9/syntaxmatrix/premium/state.py +867 -0
- syntaxmatrix_core-1.0.9/syntaxmatrix/premium/verify.py +357 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/settings/model_map.py +149 -16
- syntaxmatrix_core-1.0.9/syntaxmatrix/settings/prompts.py +3656 -0
- syntaxmatrix_core-1.0.9/syntaxmatrix/static/icons/bot_icon.png +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9/syntaxmatrix_core.egg-info}/PKG-INFO +1 -1
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix_core.egg-info/SOURCES.txt +5 -0
- syntaxmatrix_core-1.0.8/syntaxmatrix/premium/__init__.py +0 -10
- syntaxmatrix_core-1.0.8/syntaxmatrix/settings/prompts.py +0 -1755
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/MANIFEST.in +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/README.md +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/pyproject.toml +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/setup.cfg +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/agent_tools.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/agents.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/code_tools_registry.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/model_templates.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/auth.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/bootstrap.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/client_docs.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/commentary.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/core.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/dataset_preprocessing.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/db.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/db_backends/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/db_backends/postgres_backend.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/db_backends/sqlite_backend.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/db_contract.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/display_html.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/emailer.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/file_processor.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/gpt_models_latest.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/history_store.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/kernel_manager.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/llm_store.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/media/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/media/media_pixabay.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/models.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/page_builder_defaults.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/page_builder_generation.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/page_layout_contract.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/page_patch_publish.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/plottings.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/plugin_manager.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/preface.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/profiles.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/project_root.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/routes.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/selftest_page_templates.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/session.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/settings/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/settings/client_items.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/settings/logging.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/settings/string_navbar.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/smiv.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/smpv.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/assets/hero-default.svg +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/css/style.css +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/docs.md +0 -0
- /syntaxmatrix_core-1.0.8/syntaxmatrix/static/icons/bot_icon.png → /syntaxmatrix_core-1.0.9/syntaxmatrix/static/icons/bot_icon2.png +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/icons/favicon.png +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/icons/logo.png +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/icons/logo2.png +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/icons/svg_497526.svg +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/icons/svg_497528.svg +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/js/chat.js +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/js/sidebar.js +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/static/js/widgets.js +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/admin_billing.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/admin_branding.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/admin_features.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/admin_secretes.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/change_password.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/code_cell.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/dashboard.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/dataset_resize.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/docs.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/edit_page.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/error.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/login.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/templates/register.html +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/themes.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/ui_modes.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/utils.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vector_db.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/adapters/__init__.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/adapters/milvus_adapter.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/adapters/pgvector_adapter.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/adapters/sqlite_adapter.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/base.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectordb/registry.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/vectorizer.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/workspace_db.py +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix_core.egg-info/dependency_links.txt +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix_core.egg-info/requires.txt +0 -0
- {syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix_core.egg-info/top_level.txt +0 -0
{syntaxmatrix_core-1.0.8 → syntaxmatrix_core-1.0.9}/syntaxmatrix/agentic/agents_orchestrer.py
RENAMED
|
@@ -92,10 +92,11 @@ class OrchestrateMLSystem:
|
|
|
92
92
|
def reasoning_and_verbosity():
|
|
93
93
|
reasoning_effort, verbosity = "medium", "medium"
|
|
94
94
|
if "mini" not in model:
|
|
95
|
-
if
|
|
95
|
+
if "nano" in model:
|
|
96
96
|
reasoning_effort = "low"
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
elif "high" in model:
|
|
98
|
+
reasoning_effort, verbosity = "high", "high"
|
|
99
|
+
|
|
99
100
|
return reasoning_effort, verbosity
|
|
100
101
|
|
|
101
102
|
reasoning, verbosity = reasoning_and_verbosity()
|
|
@@ -324,7 +325,7 @@ class OrchestrateMLSystem:
|
|
|
324
325
|
if not coder_profile:
|
|
325
326
|
return "Error!: Set an appropriate coder profile"
|
|
326
327
|
|
|
327
|
-
thought, raw_code, usage = self._generate_ml_response(coder_profile
|
|
328
|
+
thought, raw_code, usage = self._generate_ml_response(coder_profile,system_prompt, user_prompt)
|
|
328
329
|
|
|
329
330
|
# Robustly strip any potential markdown formatting
|
|
330
331
|
code = re.sub(r'```python|```', '', raw_code).strip()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Premium support.
|
|
2
|
+
|
|
3
|
+
This package contains runtime plumbing for premium features (entitlements +
|
|
4
|
+
plugin loading).
|
|
5
|
+
|
|
6
|
+
Key idea:
|
|
7
|
+
- FeatureGate reads a single entitlement dict from env/db/licence.json
|
|
8
|
+
- ensure_premium_state() resolves trial + plan entitlements and writes them
|
|
9
|
+
into app_settings so the rest of the app can safely gate routes + navbar.
|
|
10
|
+
|
|
11
|
+
Premium implementations (e.g. non-SQLite DB backends) should live in separate,
|
|
12
|
+
private distributions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .gate import FeatureGate
|
|
16
|
+
from .state import ensure_premium_state, PremiumState, clear_premium_state_cache
|
|
17
|
+
|
|
18
|
+
__all__ = ["FeatureGate", "ensure_premium_state", "PremiumState", "clear_premium_state_cache"]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, Optional, Tuple
|
|
6
|
+
from importlib import resources as _resources
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _safe_json_loads(raw: str, default: Any) -> Any:
|
|
10
|
+
try:
|
|
11
|
+
return json.loads(raw)
|
|
12
|
+
except Exception:
|
|
13
|
+
return default
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _read_json(path: str) -> Dict[str, Any]:
|
|
17
|
+
try:
|
|
18
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
19
|
+
data = json.load(f)
|
|
20
|
+
return data if isinstance(data, dict) else {}
|
|
21
|
+
except Exception:
|
|
22
|
+
return {}
|
|
23
|
+
##########################################################
|
|
24
|
+
def _read_json_resource(name: str) -> Dict[str, Any]:
|
|
25
|
+
"""Read JSON packaged with the module (works even when running from a wheel)."""
|
|
26
|
+
try:
|
|
27
|
+
raw = _resources.files(__package__).joinpath(name).read_text(encoding="utf-8")
|
|
28
|
+
data = _safe_json_loads(raw, {})
|
|
29
|
+
return data if isinstance(data, dict) else {}
|
|
30
|
+
except Exception:
|
|
31
|
+
return {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_FALLBACK_INDEX: Dict[str, Any] = {
|
|
35
|
+
"default": "2026-01-19",
|
|
36
|
+
"versions": {"2026-01-19": "catalogue.v1.json"},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_FALLBACK_CATALOGUE_V1: Dict[str, Any] = {
|
|
40
|
+
"schema": "smx-premium-catalogue/v1",
|
|
41
|
+
"version": "2026-01-19",
|
|
42
|
+
"currency": "EUR",
|
|
43
|
+
"plans": {
|
|
44
|
+
"trial": {
|
|
45
|
+
"display_name": "Trial",
|
|
46
|
+
"entitlements": {
|
|
47
|
+
"docs": True,
|
|
48
|
+
"ml_lab": True,
|
|
49
|
+
"registration": True,
|
|
50
|
+
"theme_toggle": True,
|
|
51
|
+
"branding_controls": True,
|
|
52
|
+
"plugins": True,
|
|
53
|
+
"premium_db_backends": True,
|
|
54
|
+
"audit_export": True,
|
|
55
|
+
"max_users": 9999,
|
|
56
|
+
"max_pages": 9999,
|
|
57
|
+
"max_upload_mb": 9999,
|
|
58
|
+
"max_pdf_pages_per_doc": 999999,
|
|
59
|
+
"max_vector_records": 999999999,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
"free": {
|
|
63
|
+
"display_name": "Free",
|
|
64
|
+
"entitlements": {
|
|
65
|
+
"docs": False,
|
|
66
|
+
"ml_lab": False,
|
|
67
|
+
"registration": False,
|
|
68
|
+
"theme_toggle": False,
|
|
69
|
+
"branding_controls": False,
|
|
70
|
+
"plugins": False,
|
|
71
|
+
"premium_db_backends": False,
|
|
72
|
+
"audit_export": False,
|
|
73
|
+
"max_users": 1,
|
|
74
|
+
"max_pages": 3,
|
|
75
|
+
"max_upload_mb": 5,
|
|
76
|
+
"max_pdf_pages_per_doc": 20,
|
|
77
|
+
"max_vector_records": 1500,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
"pro": {
|
|
81
|
+
"display_name": "Pro",
|
|
82
|
+
"entitlements": {
|
|
83
|
+
"docs": True,
|
|
84
|
+
"ml_lab": True,
|
|
85
|
+
"registration": True,
|
|
86
|
+
"theme_toggle": True,
|
|
87
|
+
"branding_controls": True,
|
|
88
|
+
"plugins": True,
|
|
89
|
+
"premium_db_backends": False,
|
|
90
|
+
"audit_export": True,
|
|
91
|
+
"max_users": 10,
|
|
92
|
+
"max_pages": 50,
|
|
93
|
+
"max_upload_mb": 200,
|
|
94
|
+
"max_pdf_pages_per_doc": 300,
|
|
95
|
+
"max_vector_records": 50000,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
"business": {
|
|
99
|
+
"display_name": "Business",
|
|
100
|
+
"entitlements": {
|
|
101
|
+
"docs": True,
|
|
102
|
+
"ml_lab": True,
|
|
103
|
+
"registration": True,
|
|
104
|
+
"theme_toggle": True,
|
|
105
|
+
"branding_controls": True,
|
|
106
|
+
"plugins": True,
|
|
107
|
+
"premium_db_backends": True,
|
|
108
|
+
"audit_export": True,
|
|
109
|
+
"max_users": 25,
|
|
110
|
+
"max_pages": 150,
|
|
111
|
+
"max_upload_mb": 500,
|
|
112
|
+
"max_pdf_pages_per_doc": 750,
|
|
113
|
+
"max_vector_records": 200000,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
"enterprise": {
|
|
117
|
+
"display_name": "Enterprise",
|
|
118
|
+
"entitlements": {
|
|
119
|
+
"docs": True,
|
|
120
|
+
"ml_lab": True,
|
|
121
|
+
"registration": True,
|
|
122
|
+
"theme_toggle": True,
|
|
123
|
+
"branding_controls": True,
|
|
124
|
+
"plugins": True,
|
|
125
|
+
"premium_db_backends": True,
|
|
126
|
+
"audit_export": True,
|
|
127
|
+
"max_users": 200,
|
|
128
|
+
"max_pages": 500,
|
|
129
|
+
"max_upload_mb": 2000,
|
|
130
|
+
"max_pdf_pages_per_doc": 2000,
|
|
131
|
+
"max_vector_records": 500000,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
"addons": {},
|
|
136
|
+
"ui": {
|
|
137
|
+
"labels": {
|
|
138
|
+
"docs": {"name": "Docs", "category": "Features", "type": "bool"},
|
|
139
|
+
"ml_lab": {"name": "ML Lab", "category": "Features", "type": "bool"},
|
|
140
|
+
"registration": {"name": "Registration", "category": "Features", "type": "bool"},
|
|
141
|
+
"theme_toggle": {"name": "Theme toggle", "category": "Features", "type": "bool"},
|
|
142
|
+
"branding_controls": {"name": "Branding controls", "category": "Features", "type": "bool"},
|
|
143
|
+
"plugins": {"name": "Plugins", "category": "Features", "type": "bool"},
|
|
144
|
+
"premium_db_backends": {"name": "Premium DB backends", "category": "Features", "type": "bool"},
|
|
145
|
+
"audit_export": {"name": "Audit/export", "category": "Features", "type": "bool"},
|
|
146
|
+
"max_users": {"name": "Max users", "category": "Limits", "type": "int", "unit": "users"},
|
|
147
|
+
"max_pages": {"name": "Max pages", "category": "Limits", "type": "int", "unit": "pages"},
|
|
148
|
+
"max_upload_mb": {"name": "Max upload size", "category": "Limits", "type": "int", "unit": "MB"},
|
|
149
|
+
"max_pdf_pages_per_doc": {
|
|
150
|
+
"name": "Max PDF pages per document",
|
|
151
|
+
"category": "Limits",
|
|
152
|
+
"type": "int",
|
|
153
|
+
"unit": "pages",
|
|
154
|
+
},
|
|
155
|
+
"max_vector_records": {"name": "Max vector records", "category": "Limits", "type": "int", "unit": "records"},
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
#########################################
|
|
160
|
+
|
|
161
|
+
def _catalogue_dir() -> str:
|
|
162
|
+
return os.path.join(os.path.dirname(__file__))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def load_index() -> Dict[str, Any]:
|
|
166
|
+
"""Load catalogue index.json (safe, returns fallback on failure)."""
|
|
167
|
+
data = _read_json(os.path.join(_catalogue_dir(), "index.json"))
|
|
168
|
+
if data:
|
|
169
|
+
return data
|
|
170
|
+
data = _read_json_resource("index.json")
|
|
171
|
+
return data if data else dict(_FALLBACK_INDEX)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def load_catalogue(version: Optional[str] = None) -> Tuple[Dict[str, Any], str]:
|
|
175
|
+
"""Load a catalogue for the requested version.
|
|
176
|
+
|
|
177
|
+
Returns: (catalogue_dict, resolved_version)
|
|
178
|
+
"""
|
|
179
|
+
idx = load_index()
|
|
180
|
+
resolved = (version or "").strip() or str(idx.get("default") or "").strip()
|
|
181
|
+
versions = idx.get("versions") if isinstance(idx.get("versions"), dict) else {}
|
|
182
|
+
fname = versions.get(resolved) if isinstance(versions, dict) else None
|
|
183
|
+
|
|
184
|
+
if not fname:
|
|
185
|
+
# Fall back to default file name if index is missing or version unknown.
|
|
186
|
+
fname = "catalogue.v1.json"
|
|
187
|
+
|
|
188
|
+
cat = _read_json(os.path.join(_catalogue_dir(), str(fname)))
|
|
189
|
+
if not cat:
|
|
190
|
+
cat = _read_json_resource(str(fname))
|
|
191
|
+
if not cat:
|
|
192
|
+
cat = dict(_FALLBACK_CATALOGUE_V1)
|
|
193
|
+
|
|
194
|
+
# If the loaded file advertises a version, prefer that.
|
|
195
|
+
advertised = str(cat.get("version") or "").strip()
|
|
196
|
+
if advertised:
|
|
197
|
+
resolved = advertised
|
|
198
|
+
return cat, resolved
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def ui_labels(version: Optional[str] = None) -> Dict[str, Any]:
|
|
202
|
+
cat, _ = load_catalogue(version)
|
|
203
|
+
ui = cat.get("ui") if isinstance(cat.get("ui"), dict) else {}
|
|
204
|
+
labels = ui.get("labels") if isinstance(ui.get("labels"), dict) else {}
|
|
205
|
+
return labels if isinstance(labels, dict) else {}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def resolve_entitlements(
|
|
209
|
+
*,
|
|
210
|
+
plan_id: str,
|
|
211
|
+
version: Optional[str] = None,
|
|
212
|
+
addons: Optional[list] = None,
|
|
213
|
+
custom_overrides: Optional[Dict[str, Any]] = None,
|
|
214
|
+
) -> Dict[str, Any]:
|
|
215
|
+
"""Resolve final entitlements from catalogue.
|
|
216
|
+
|
|
217
|
+
This keeps entitlements data-driven:
|
|
218
|
+
- base entitlements come from the plan in the catalogue
|
|
219
|
+
- optional add-ons apply deltas/overrides
|
|
220
|
+
- optional custom_overrides apply last
|
|
221
|
+
"""
|
|
222
|
+
pid = (plan_id or "free").strip().lower() or "free"
|
|
223
|
+
cat, resolved_version = load_catalogue(version)
|
|
224
|
+
|
|
225
|
+
plans = cat.get("plans") if isinstance(cat.get("plans"), dict) else {}
|
|
226
|
+
p = plans.get(pid) if isinstance(plans, dict) else None
|
|
227
|
+
base = {}
|
|
228
|
+
if isinstance(p, dict) and isinstance(p.get("entitlements"), dict):
|
|
229
|
+
base = dict(p.get("entitlements") or {})
|
|
230
|
+
|
|
231
|
+
# Always stamp plan + version for UI/debugging.
|
|
232
|
+
base["plan"] = pid
|
|
233
|
+
base["entitlement_version"] = resolved_version
|
|
234
|
+
|
|
235
|
+
# Apply add-ons.
|
|
236
|
+
addons_list = addons if isinstance(addons, list) else []
|
|
237
|
+
if addons_list:
|
|
238
|
+
addons_def = cat.get("addons") if isinstance(cat.get("addons"), dict) else {}
|
|
239
|
+
for addon_id in addons_list:
|
|
240
|
+
aid = str(addon_id or "").strip()
|
|
241
|
+
if not aid:
|
|
242
|
+
continue
|
|
243
|
+
a = addons_def.get(aid) if isinstance(addons_def, dict) else None
|
|
244
|
+
if not isinstance(a, dict):
|
|
245
|
+
continue
|
|
246
|
+
delta = a.get("entitlements_delta") if isinstance(a.get("entitlements_delta"), dict) else {}
|
|
247
|
+
for k, v in (delta or {}).items():
|
|
248
|
+
# Support "+N" increment for numeric limits.
|
|
249
|
+
if isinstance(v, str) and v.strip().startswith("+"):
|
|
250
|
+
try:
|
|
251
|
+
inc = int(v.strip()[1:])
|
|
252
|
+
cur = int(base.get(k) or 0)
|
|
253
|
+
base[k] = cur + inc
|
|
254
|
+
continue
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
base[k] = v
|
|
258
|
+
base["addons"] = addons_list
|
|
259
|
+
|
|
260
|
+
# Apply overrides last.
|
|
261
|
+
if isinstance(custom_overrides, dict) and custom_overrides:
|
|
262
|
+
for k, v in custom_overrides.items():
|
|
263
|
+
if k in ("sig",):
|
|
264
|
+
continue
|
|
265
|
+
base[k] = v
|
|
266
|
+
|
|
267
|
+
return base
|
|
@@ -3,8 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
6
7
|
from typing import Any, Dict, Optional
|
|
7
8
|
|
|
9
|
+
from .verify import verify_licence_payload
|
|
10
|
+
from .runtime_paths import runtime_lease_path
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
def _safe_json_loads(raw: str, *, default: Any) -> Any:
|
|
10
14
|
try:
|
|
@@ -15,24 +19,14 @@ def _safe_json_loads(raw: str, *, default: Any) -> Any:
|
|
|
15
19
|
|
|
16
20
|
@dataclass
|
|
17
21
|
class GateSources:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Precedence (highest first):
|
|
21
|
-
1) env_json
|
|
22
|
-
2) db_setting_key
|
|
23
|
-
3) licence_file
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
env_json: str = "SMX_PREMIUM_ENTITLEMENTS"
|
|
27
|
-
db_setting_key: str = "premium.entitlements"
|
|
28
|
-
licence_file_relpath: str = os.path.join("premium", "licence.json")
|
|
22
|
+
|
|
23
|
+
lease_file_relpath: str = os.path.join("premium", "runtime_lease.json")
|
|
29
24
|
|
|
30
25
|
|
|
31
26
|
class FeatureGate:
|
|
32
27
|
"""Runtime entitlement checks for premium features.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- If anything fails, it returns 'not entitled' rather than crashing the app.
|
|
28
|
+
The gate now trusts only a signed runtime lease that is bound to the
|
|
29
|
+
instance and current runtime fingerprint.
|
|
36
30
|
"""
|
|
37
31
|
|
|
38
32
|
def __init__(
|
|
@@ -47,45 +41,36 @@ class FeatureGate:
|
|
|
47
41
|
self._sources = sources or GateSources()
|
|
48
42
|
self._cache: Optional[Dict[str, Any]] = None
|
|
49
43
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
if not raw:
|
|
44
|
+
def _normalise(self, data: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
45
|
+
if not isinstance(data, dict):
|
|
53
46
|
return None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
get_setting = getattr(self._db, "get_setting", None)
|
|
61
|
-
if not callable(get_setting):
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
raw = get_setting(self._sources.db_setting_key, "{}")
|
|
65
|
-
data = _safe_json_loads(str(raw or "{}"), default={})
|
|
66
|
-
return data if isinstance(data, dict) else None
|
|
47
|
+
if isinstance(data.get("entitlements"), dict):
|
|
48
|
+
ent = dict(data.get("entitlements") or {})
|
|
49
|
+
if data.get("plan") and not ent.get("plan"):
|
|
50
|
+
ent["plan"] = data.get("plan")
|
|
51
|
+
return ent
|
|
52
|
+
return data
|
|
67
53
|
|
|
68
54
|
def _load_from_file(self) -> Optional[Dict[str, Any]]:
|
|
69
|
-
p =
|
|
55
|
+
p = runtime_lease_path(self._client_dir)
|
|
70
56
|
if not os.path.exists(p):
|
|
71
57
|
return None
|
|
72
58
|
try:
|
|
73
59
|
with open(p, "r", encoding="utf-8") as f:
|
|
74
60
|
data = json.load(f)
|
|
75
|
-
return data if isinstance(data, dict) else None
|
|
76
61
|
except Exception:
|
|
77
62
|
return None
|
|
78
63
|
|
|
64
|
+
verified = verify_licence_payload(data, now=datetime.utcnow(), allow_unsigned=False)
|
|
65
|
+
if not verified.ok:
|
|
66
|
+
return None
|
|
67
|
+
return self._normalise(verified.entitlements)
|
|
68
|
+
|
|
79
69
|
def entitlements(self, *, refresh: bool = False) -> Dict[str, Any]:
|
|
80
|
-
"""Returns entitlement dict (possibly empty)."""
|
|
81
70
|
if self._cache is not None and not refresh:
|
|
82
71
|
return self._cache
|
|
83
72
|
|
|
84
|
-
ent = self.
|
|
85
|
-
if ent is None:
|
|
86
|
-
ent = self._load_from_db()
|
|
87
|
-
if ent is None:
|
|
88
|
-
ent = self._load_from_file()
|
|
73
|
+
ent = self._load_from_file()
|
|
89
74
|
if ent is None:
|
|
90
75
|
ent = {}
|
|
91
76
|
|
|
@@ -93,7 +78,6 @@ class FeatureGate:
|
|
|
93
78
|
return ent
|
|
94
79
|
|
|
95
80
|
def enabled(self, key: str) -> bool:
|
|
96
|
-
"""True if entitlement exists and is truthy."""
|
|
97
81
|
key = (key or "").strip()
|
|
98
82
|
if not key:
|
|
99
83
|
return False
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_RUNTIME_ENV_NAMES = ("SMX_PREMIUM_RUNTIME_DIR", "SMX_RUNTIME_DIR")
|
|
10
|
+
_AUDIT_ENV_NAME = "SMX_PREMIUM_AUDIT_DIR"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _clean(value: str) -> str:
|
|
14
|
+
return str(value or "").strip()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _ensure_dir(path: str) -> str:
|
|
18
|
+
if not path:
|
|
19
|
+
return path
|
|
20
|
+
os.makedirs(path, exist_ok=True)
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _stable_runtime_bucket_name(client_dir: str) -> str:
|
|
25
|
+
raw = _clean(client_dir) or "default"
|
|
26
|
+
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
|
27
|
+
return f"smx_runtime_{digest}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _default_runtime_root(client_dir: str) -> str:
|
|
31
|
+
base_tmp = tempfile.gettempdir()
|
|
32
|
+
leaf = _stable_runtime_bucket_name(client_dir)
|
|
33
|
+
return os.path.join(base_tmp, leaf)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_runtime_root(client_dir: str, *, ensure: bool = True) -> str:
|
|
37
|
+
root = ""
|
|
38
|
+
for env_name in _RUNTIME_ENV_NAMES:
|
|
39
|
+
candidate = _clean(os.environ.get(env_name))
|
|
40
|
+
if candidate:
|
|
41
|
+
root = candidate
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if not root:
|
|
45
|
+
root = _default_runtime_root(client_dir)
|
|
46
|
+
|
|
47
|
+
if ensure and root:
|
|
48
|
+
try:
|
|
49
|
+
_ensure_dir(root)
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
return root
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def resolve_runtime_premium_dir(client_dir: str, *, ensure: bool = True) -> str:
|
|
56
|
+
root = resolve_runtime_root(client_dir, ensure=ensure)
|
|
57
|
+
path = os.path.join(root, "premium") if root else os.path.join("premium")
|
|
58
|
+
if ensure:
|
|
59
|
+
try:
|
|
60
|
+
_ensure_dir(path)
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
return path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def resolve_audit_root(client_dir: str, *, ensure: bool = True) -> str:
|
|
67
|
+
root = _clean(os.environ.get(_AUDIT_ENV_NAME)) or _clean(client_dir)
|
|
68
|
+
if ensure and root:
|
|
69
|
+
try:
|
|
70
|
+
_ensure_dir(root)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
return root
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_audit_premium_dir(client_dir: str, *, ensure: bool = True) -> str:
|
|
77
|
+
root = resolve_audit_root(client_dir, ensure=ensure)
|
|
78
|
+
path = os.path.join(root, "premium") if root else os.path.join("premium")
|
|
79
|
+
if ensure:
|
|
80
|
+
try:
|
|
81
|
+
_ensure_dir(path)
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
return path
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def runtime_lease_path(client_dir: str) -> str:
|
|
88
|
+
return os.path.join(resolve_runtime_premium_dir(client_dir), "runtime_lease.json")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def tamper_queue_path(client_dir: str) -> str:
|
|
92
|
+
return os.path.join(resolve_runtime_premium_dir(client_dir), "tamper_queue.jsonl")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def tamper_audit_path(client_dir: str) -> str:
|
|
96
|
+
return os.path.join(resolve_audit_premium_dir(client_dir), "tamper_audit.log")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def describe_premium_paths(client_dir: str) -> Dict[str, str]:
|
|
100
|
+
runtime_root = resolve_runtime_root(client_dir, ensure=False)
|
|
101
|
+
audit_root = resolve_audit_root(client_dir, ensure=False)
|
|
102
|
+
return {
|
|
103
|
+
"client_dir": _clean(client_dir),
|
|
104
|
+
"runtime_root": runtime_root,
|
|
105
|
+
"runtime_lease_path": os.path.join(runtime_root, "premium", "runtime_lease.json") if runtime_root else "",
|
|
106
|
+
"tamper_queue_path": os.path.join(runtime_root, "premium", "tamper_queue.jsonl") if runtime_root else "",
|
|
107
|
+
"audit_root": audit_root,
|
|
108
|
+
"tamper_audit_path": os.path.join(audit_root, "premium", "tamper_audit.log") if audit_root else "",
|
|
109
|
+
}
|