zikriyon-prism 0.1.0__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.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: zikriyon-prism
3
+ Version: 0.1.0
4
+ Summary: Security, moderation and trust scoring engine — local rule engine plus optional cloud ML tier.
5
+ Author: ZEAIPC (Zikr-e-Ameen Innovations & Programming Corporation)
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://zikriyonprism.dev
8
+ Project-URL: Repository, https://github.com/zeaipc/zikriyon-prism
9
+ Keywords: security,moderation,trust-score,spam-detection,anomaly-detection
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Security
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: PyYAML>=6.0
21
+ Requires-Dist: httpx>=0.25
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.4; extra == "dev"
24
+ Requires-Dist: mypy>=1.5; extra == "dev"
25
+ Requires-Dist: ruff>=0.1; extra == "dev"
26
+
27
+ # zikriyon-prism (Python SDK)
28
+
29
+ Security, moderation and trust-scoring engine — local rule engine
30
+ (free, fully offline) plus an optional cloud ML tier (trust score,
31
+ anomaly detection, phishing NLP).
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install zikriyon-prism
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ from zikriyon_prism import RuleEngine
43
+
44
+ engine = RuleEngine.from_file("rules/spam_abuse.yaml")
45
+ result = engine.evaluate(
46
+ {"message": {"text": "click here to unlock your account"}},
47
+ subject_id="user_123",
48
+ )
49
+ print(result.action) # "block"
50
+ ```
51
+
52
+ ## Paid tier
53
+
54
+ ```python
55
+ from zikriyon_prism import CloudClient
56
+
57
+ with CloudClient(license_key="ZP-PRO-<32 hex chars>") as client:
58
+ score = client.get_trust_score("user_123", signals={"login_count": 5})
59
+ ```
60
+
61
+ License keys are validated for *shape* client-side only. Real
62
+ entitlement and quota checks always happen server-side.
63
+
64
+ Full project docs, the Node.js SDK, and the cloud service:
65
+ https://github.com/zeaipc/zikriyon-prism
@@ -0,0 +1,39 @@
1
+ # zikriyon-prism (Python SDK)
2
+
3
+ Security, moderation and trust-scoring engine — local rule engine
4
+ (free, fully offline) plus an optional cloud ML tier (trust score,
5
+ anomaly detection, phishing NLP).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install zikriyon-prism
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from zikriyon_prism import RuleEngine
17
+
18
+ engine = RuleEngine.from_file("rules/spam_abuse.yaml")
19
+ result = engine.evaluate(
20
+ {"message": {"text": "click here to unlock your account"}},
21
+ subject_id="user_123",
22
+ )
23
+ print(result.action) # "block"
24
+ ```
25
+
26
+ ## Paid tier
27
+
28
+ ```python
29
+ from zikriyon_prism import CloudClient
30
+
31
+ with CloudClient(license_key="ZP-PRO-<32 hex chars>") as client:
32
+ score = client.get_trust_score("user_123", signals={"login_count": 5})
33
+ ```
34
+
35
+ License keys are validated for *shape* client-side only. Real
36
+ entitlement and quota checks always happen server-side.
37
+
38
+ Full project docs, the Node.js SDK, and the cloud service:
39
+ https://github.com/zeaipc/zikriyon-prism
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zikriyon-prism"
7
+ version = "0.1.0"
8
+ description = "Security, moderation and trust scoring engine — local rule engine plus optional cloud ML tier."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [{ name = "ZEAIPC (Zikr-e-Ameen Innovations & Programming Corporation)" }]
13
+ keywords = ["security", "moderation", "trust-score", "spam-detection", "anomaly-detection"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Security",
23
+ ]
24
+ dependencies = [
25
+ "PyYAML>=6.0",
26
+ "httpx>=0.25",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=7.4",
32
+ "mypy>=1.5",
33
+ "ruff>=0.1",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://zikriyonprism.dev"
38
+ Repository = "https://github.com/zeaipc/zikriyon-prism"
39
+
40
+ [tool.setuptools.packages.find]
41
+ include = ["zikriyon_prism*"]
42
+
43
+ [tool.mypy]
44
+ strict = true
45
+ python_version = "3.9"
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py39"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,94 @@
1
+ import time
2
+
3
+ import pytest
4
+
5
+ from zikriyon_prism.engine import RuleEngine, RuleSpecError
6
+
7
+ SPEC = """
8
+ version: 1
9
+ spec_id: test_spec
10
+ settings:
11
+ case_sensitive: false
12
+ default_action: allow
13
+ max_severity_wins: true
14
+ severity_levels:
15
+ low: { weight: 1, action: flag }
16
+ high: { weight: 3, action: block }
17
+ rules:
18
+ - id: phishing_test
19
+ type: keyword_list
20
+ target: message.text
21
+ list_ref: keyword_lists.phishing
22
+ severity: high
23
+ reason: "phishing phrase"
24
+ - id: flood_test
25
+ type: rate
26
+ target: user.message_count
27
+ threshold: 3
28
+ window_seconds: 5
29
+ severity: low
30
+ reason: "too many messages"
31
+ keyword_lists:
32
+ phishing:
33
+ - "verify your account"
34
+ """
35
+
36
+
37
+ @pytest.fixture
38
+ def engine() -> RuleEngine:
39
+ return RuleEngine.from_string(SPEC)
40
+
41
+
42
+ def test_no_match_returns_default_action(engine: RuleEngine) -> None:
43
+ result = engine.evaluate({"message": {"text": "hello there"}}, subject_id="u1")
44
+ assert result.matched is False
45
+ assert result.action == "allow"
46
+
47
+
48
+ def test_keyword_match_blocks(engine: RuleEngine) -> None:
49
+ result = engine.evaluate(
50
+ {"message": {"text": "please verify your account now"}}, subject_id="u1"
51
+ )
52
+ assert result.matched is True
53
+ assert result.action == "block"
54
+ assert result.matches[0].rule_id == "phishing_test"
55
+
56
+
57
+ def test_case_insensitive_by_default(engine: RuleEngine) -> None:
58
+ result = engine.evaluate(
59
+ {"message": {"text": "VERIFY YOUR ACCOUNT"}}, subject_id="u1"
60
+ )
61
+ assert result.matched is True
62
+
63
+
64
+ def test_rate_rule_triggers_after_threshold(engine: RuleEngine) -> None:
65
+ subject = "flood_user"
66
+ results = [
67
+ engine.evaluate({"message": {"text": "hi"}}, subject_id=subject)
68
+ for _ in range(3)
69
+ ]
70
+ assert results[-1].matched is True
71
+ assert results[-1].action == "flag"
72
+
73
+
74
+ def test_rate_rule_resets_outside_window() -> None:
75
+ engine = RuleEngine.from_string(SPEC.replace("window_seconds: 5", "window_seconds: 0.2"))
76
+ subj = "u2"
77
+ engine.evaluate({}, subject_id=subj)
78
+ time.sleep(0.3)
79
+ result = engine.evaluate({}, subject_id=subj)
80
+ assert result.matched is False
81
+
82
+
83
+ def test_invalid_spec_rejected() -> None:
84
+ with pytest.raises(RuleSpecError):
85
+ RuleEngine.from_string("rules: []") # missing severity_levels
86
+
87
+
88
+ def test_malformed_rule_does_not_crash_evaluation() -> None:
89
+ bad_spec = SPEC + "\n - id: broken\n type: pattern\n severity: high\n"
90
+ engine = RuleEngine.from_string(bad_spec)
91
+ # 'pattern' rule missing required 'pattern'/'target' keys should be
92
+ # skipped, not raise, per production error-isolation requirement.
93
+ result = engine.evaluate({"message": {"text": "safe text"}}, subject_id="u3")
94
+ assert result.matched is False
@@ -0,0 +1,35 @@
1
+ """
2
+ Zikriyon Prism — security & moderation engine for QalbConverfy and beyond.
3
+
4
+ Free tier (no license needed):
5
+ from zikriyon_prism import RuleEngine
6
+ engine = RuleEngine.from_file("rules/spam_abuse.yaml")
7
+ result = engine.evaluate({"message": {"text": "..."}}, subject_id="user_1")
8
+
9
+ Paid tier (license key required):
10
+ from zikriyon_prism import CloudClient
11
+ with CloudClient(license_key="ZP-PRO-...") as client:
12
+ score = client.get_trust_score("user_1", signals={...})
13
+ """
14
+
15
+ from .cloud_client import CloudClient, CloudServiceError, LicenseRequiredError, TrustScoreResult
16
+ from .engine import EvaluationResult, RuleEngine, RuleMatch, RuleSpecError
17
+ from .license import InvalidLicenseKeyError, LicenseKey, load_license, parse_license
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ __all__ = [
22
+ "RuleEngine",
23
+ "EvaluationResult",
24
+ "RuleMatch",
25
+ "RuleSpecError",
26
+ "CloudClient",
27
+ "CloudServiceError",
28
+ "LicenseRequiredError",
29
+ "TrustScoreResult",
30
+ "LicenseKey",
31
+ "InvalidLicenseKeyError",
32
+ "load_license",
33
+ "parse_license",
34
+ "__version__",
35
+ ]
@@ -0,0 +1,101 @@
1
+ """
2
+ Zikriyon Prism — cloud client (paid tier).
3
+
4
+ Thin HTTP client for the hosted ML endpoints (trust score, anomaly
5
+ detection, phishing NLP). Requires a valid license key. Never used
6
+ by the free local rule engine.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from dataclasses import dataclass
13
+ from typing import Any, Dict, Optional
14
+
15
+ import httpx
16
+
17
+ from .license import LicenseKey, load_license
18
+
19
+ logger = logging.getLogger("zikriyon_prism.cloud_client")
20
+
21
+ DEFAULT_BASE_URL = "https://api.zikriyonprism.dev/v1"
22
+
23
+
24
+ class CloudServiceError(RuntimeError):
25
+ pass
26
+
27
+
28
+ class LicenseRequiredError(RuntimeError):
29
+ pass
30
+
31
+
32
+ @dataclass
33
+ class TrustScoreResult:
34
+ user_id: str
35
+ score: float # 0.0 (untrusted) - 1.0 (fully trusted)
36
+ factors: Dict[str, float]
37
+
38
+
39
+ class CloudClient:
40
+ def __init__(
41
+ self,
42
+ license_key: Optional[str] = None,
43
+ base_url: str = DEFAULT_BASE_URL,
44
+ timeout_seconds: float = 5.0,
45
+ ) -> None:
46
+ resolved = load_license(license_key)
47
+ if resolved is None:
48
+ raise LicenseRequiredError(
49
+ "CloudClient requires a license key (pass explicitly or set "
50
+ "ZIKRIYON_PRISM_KEY). The free local RuleEngine does not need one."
51
+ )
52
+ self._license: LicenseKey = resolved
53
+ self._base_url = base_url.rstrip("/")
54
+ self._client = httpx.Client(timeout=timeout_seconds)
55
+
56
+ def close(self) -> None:
57
+ self._client.close()
58
+
59
+ def __enter__(self) -> "CloudClient":
60
+ return self
61
+
62
+ def __exit__(self, *_exc: object) -> None:
63
+ self.close()
64
+
65
+ def _headers(self) -> Dict[str, str]:
66
+ return {
67
+ "Authorization": f"Bearer {self._license.raw}",
68
+ "Content-Type": "application/json",
69
+ }
70
+
71
+ def get_trust_score(self, user_id: str, signals: Dict[str, Any]) -> TrustScoreResult:
72
+ try:
73
+ resp = self._client.post(
74
+ f"{self._base_url}/trust-score",
75
+ headers=self._headers(),
76
+ json={"user_id": user_id, "signals": signals},
77
+ )
78
+ resp.raise_for_status()
79
+ except httpx.HTTPStatusError as exc:
80
+ raise CloudServiceError(
81
+ f"Trust score request failed: {exc.response.status_code} {exc.response.text}"
82
+ ) from exc
83
+ except httpx.HTTPError as exc:
84
+ raise CloudServiceError(f"Trust score request failed: {exc}") from exc
85
+
86
+ data = resp.json()
87
+ return TrustScoreResult(
88
+ user_id=user_id, score=data["score"], factors=data.get("factors", {})
89
+ )
90
+
91
+ def check_anomaly(self, subject_id: str, features: Dict[str, Any]) -> Dict[str, Any]:
92
+ try:
93
+ resp = self._client.post(
94
+ f"{self._base_url}/anomaly",
95
+ headers=self._headers(),
96
+ json={"subject_id": subject_id, "features": features},
97
+ )
98
+ resp.raise_for_status()
99
+ except httpx.HTTPError as exc:
100
+ raise CloudServiceError(f"Anomaly check failed: {exc}") from exc
101
+ return resp.json()
@@ -0,0 +1,236 @@
1
+ """
2
+ Zikriyon Prism — local rule engine (free tier).
3
+
4
+ Loads the shared, language-agnostic YAML rule spec and evaluates events
5
+ against it. Fully local, no network calls, no data leaves the process.
6
+
7
+ This module intentionally mirrors node-sdk/src/engine.ts rule-for-rule.
8
+ If you change matching semantics here, update the TypeScript port too —
9
+ the YAML spec is the shared contract, this file is the Python side of it.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import re
16
+ import time
17
+ from collections import deque
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Any, Deque, Dict, List, Optional
21
+
22
+ import yaml
23
+
24
+ logger = logging.getLogger("zikriyon_prism.engine")
25
+
26
+ _VALID_RULE_TYPES = {"pattern", "pattern_count", "keyword_list", "rate", "variance_check"}
27
+
28
+
29
+ class RuleSpecError(ValueError):
30
+ """Raised when the YAML rule spec is malformed or internally inconsistent."""
31
+
32
+
33
+ @dataclass
34
+ class RuleMatch:
35
+ rule_id: str
36
+ severity: str
37
+ action: str
38
+ reason: str
39
+ weight: int
40
+
41
+
42
+ @dataclass
43
+ class EvaluationResult:
44
+ matched: bool
45
+ action: str
46
+ matches: List[RuleMatch] = field(default_factory=list)
47
+
48
+ @property
49
+ def highest_severity_match(self) -> Optional[RuleMatch]:
50
+ if not self.matches:
51
+ return None
52
+ return max(self.matches, key=lambda m: m.weight)
53
+
54
+
55
+ class _SlidingWindowCounter:
56
+ """Tracks event timestamps per (rule_id, subject) for rate-based rules."""
57
+
58
+ def __init__(self) -> None:
59
+ self._windows: Dict[str, Deque[float]] = {}
60
+
61
+ def record_and_count(self, key: str, window_seconds: float, now: Optional[float] = None) -> int:
62
+ now = now if now is not None else time.time()
63
+ dq = self._windows.setdefault(key, deque())
64
+ dq.append(now)
65
+ cutoff = now - window_seconds
66
+ while dq and dq[0] < cutoff:
67
+ dq.popleft()
68
+ return len(dq)
69
+
70
+
71
+ class RuleEngine:
72
+ """
73
+ Loads a Zikriyon Prism YAML rule spec and evaluates events against it.
74
+
75
+ Example:
76
+ engine = RuleEngine.from_file("core-engine/rules/spam_abuse.yaml")
77
+ result = engine.evaluate({
78
+ "message": {"text": "click here to unlock your account"},
79
+ }, subject_id="user_123")
80
+ if result.matched:
81
+ print(result.action, [m.reason for m in result.matches])
82
+ """
83
+
84
+ def __init__(self, spec: Dict[str, Any]) -> None:
85
+ self._validate_spec(spec)
86
+ self.spec = spec
87
+ self.settings = spec.get("settings", {})
88
+ self.severity_levels: Dict[str, Dict[str, Any]] = spec["severity_levels"]
89
+ self.rules: List[Dict[str, Any]] = spec["rules"]
90
+ self.keyword_lists: Dict[str, List[str]] = spec.get("keyword_lists", {})
91
+ self._case_sensitive: bool = bool(self.settings.get("case_sensitive", False))
92
+ self._default_action: str = self.settings.get("default_action", "allow")
93
+ self._counters = _SlidingWindowCounter()
94
+ self._compiled_patterns: Dict[str, "re.Pattern[str]"] = {}
95
+
96
+ # ---- construction -----------------------------------------------------
97
+
98
+ @classmethod
99
+ def from_file(cls, path: str | Path) -> "RuleEngine":
100
+ path = Path(path)
101
+ with path.open("r", encoding="utf-8") as f:
102
+ spec = yaml.safe_load(f)
103
+ logger.info("Loaded rule spec '%s' from %s", spec.get("spec_id", "?"), path)
104
+ return cls(spec)
105
+
106
+ @classmethod
107
+ def from_string(cls, yaml_text: str) -> "RuleEngine":
108
+ spec = yaml.safe_load(yaml_text)
109
+ return cls(spec)
110
+
111
+ @staticmethod
112
+ def _validate_spec(spec: Dict[str, Any]) -> None:
113
+ if not isinstance(spec, dict):
114
+ raise RuleSpecError("Rule spec must be a mapping at the top level")
115
+ for required in ("severity_levels", "rules"):
116
+ if required not in spec:
117
+ raise RuleSpecError(f"Rule spec missing required key: '{required}'")
118
+ for rule in spec["rules"]:
119
+ if rule.get("type") not in _VALID_RULE_TYPES:
120
+ raise RuleSpecError(
121
+ f"Rule '{rule.get('id', '?')}' has invalid type "
122
+ f"'{rule.get('type')}'. Must be one of {_VALID_RULE_TYPES}"
123
+ )
124
+ if rule.get("severity") not in spec["severity_levels"]:
125
+ raise RuleSpecError(
126
+ f"Rule '{rule.get('id', '?')}' references unknown severity "
127
+ f"'{rule.get('severity')}'"
128
+ )
129
+
130
+ # ---- evaluation ---------------------------------------------------------
131
+
132
+ def evaluate(self, event: Dict[str, Any], subject_id: str = "default") -> EvaluationResult:
133
+ """
134
+ Evaluate a single event (e.g. an incoming message or auth attempt)
135
+ against every rule in the spec. `subject_id` scopes rate/variance
136
+ counters per user/session — pass a stable ID (user id, IP, etc).
137
+ """
138
+ matches: List[RuleMatch] = []
139
+
140
+ for rule in self.rules:
141
+ try:
142
+ if self._rule_matches(rule, event, subject_id):
143
+ sev = rule["severity"]
144
+ level = self.severity_levels[sev]
145
+ matches.append(
146
+ RuleMatch(
147
+ rule_id=rule["id"],
148
+ severity=sev,
149
+ action=level["action"],
150
+ reason=rule.get("reason", ""),
151
+ weight=level["weight"],
152
+ )
153
+ )
154
+ except Exception:
155
+ # A single malformed/failing rule must never take down
156
+ # the whole evaluation pass in production.
157
+ logger.exception("Rule '%s' raised during evaluation; skipping", rule.get("id"))
158
+
159
+ if not matches:
160
+ return EvaluationResult(matched=False, action=self._default_action)
161
+
162
+ if self.settings.get("max_severity_wins", True):
163
+ top = max(matches, key=lambda m: m.weight)
164
+ return EvaluationResult(matched=True, action=top.action, matches=matches)
165
+
166
+ return EvaluationResult(matched=True, action=matches[0].action, matches=matches)
167
+
168
+ # ---- rule type handlers -------------------------------------------------
169
+
170
+ def _rule_matches(self, rule: Dict[str, Any], event: Dict[str, Any], subject_id: str) -> bool:
171
+ rtype = rule["type"]
172
+ if rtype == "pattern":
173
+ return self._match_pattern(rule, event)
174
+ if rtype == "pattern_count":
175
+ return self._match_pattern_count(rule, event)
176
+ if rtype == "keyword_list":
177
+ return self._match_keyword_list(rule, event)
178
+ if rtype == "rate":
179
+ return self._match_rate(rule, event, subject_id)
180
+ if rtype == "variance_check":
181
+ return self._match_variance(rule, event)
182
+ return False
183
+
184
+ def _get_target(self, target_path: str, event: Dict[str, Any]) -> Any:
185
+ node: Any = event
186
+ for part in target_path.split("."):
187
+ if not isinstance(node, dict) or part not in node:
188
+ return None
189
+ node = node[part]
190
+ return node
191
+
192
+ def _compile(self, pattern: str) -> "re.Pattern[str]":
193
+ if pattern not in self._compiled_patterns:
194
+ flags = 0 if self._case_sensitive else re.IGNORECASE
195
+ self._compiled_patterns[pattern] = re.compile(pattern, flags)
196
+ return self._compiled_patterns[pattern]
197
+
198
+ def _match_pattern(self, rule: Dict[str, Any], event: Dict[str, Any]) -> bool:
199
+ text = self._get_target(rule["target"], event)
200
+ if not isinstance(text, str):
201
+ return False
202
+ return self._compile(rule["pattern"]).search(text) is not None
203
+
204
+ def _match_pattern_count(self, rule: Dict[str, Any], event: Dict[str, Any]) -> bool:
205
+ text = self._get_target(rule["target"], event)
206
+ if not isinstance(text, str):
207
+ return False
208
+ count = len(self._compile(rule["pattern"]).findall(text))
209
+ return count >= rule["threshold"]
210
+
211
+ def _match_keyword_list(self, rule: Dict[str, Any], event: Dict[str, Any]) -> bool:
212
+ text = self._get_target(rule["target"], event)
213
+ if not isinstance(text, str):
214
+ return False
215
+ haystack = text if self._case_sensitive else text.lower()
216
+ list_ref = rule["list_ref"].split(".", 1)[1] # "keyword_lists.X" -> "X"
217
+ keywords = self.keyword_lists.get(list_ref, [])
218
+ for kw in keywords:
219
+ needle = kw if self._case_sensitive else kw.lower()
220
+ if needle and needle in haystack:
221
+ return True
222
+ return False
223
+
224
+ def _match_rate(self, rule: Dict[str, Any], event: Dict[str, Any], subject_id: str) -> bool:
225
+ # `target` here identifies the counter stream, not a literal value lookup.
226
+ key = f"{rule['id']}:{subject_id}"
227
+ count = self._counters.record_and_count(key, rule["window_seconds"])
228
+ return count >= rule["threshold"]
229
+
230
+ def _match_variance(self, rule: Dict[str, Any], event: Dict[str, Any]) -> bool:
231
+ samples = self._get_target(rule["target"], event)
232
+ if not isinstance(samples, list) or len(samples) < rule.get("min_samples", 5):
233
+ return False
234
+ mean = sum(samples) / len(samples)
235
+ variance = sum((x - mean) ** 2 for x in samples) / len(samples)
236
+ return variance <= rule.get("max_variance_ms", 50) ** 2
@@ -0,0 +1,57 @@
1
+ """
2
+ Zikriyon Prism — license key handling.
3
+
4
+ The local rule engine (engine.py) never requires a license — it is fully
5
+ free and offline. A license key is only required when using cloud_client.py
6
+ to call the hosted ML tier (trust score, anomaly detection, phishing NLP).
7
+
8
+ Key format: ZP-<plan>-<32 hex chars>, e.g. ZP-PRO-9f1c2e4a7b3d4f5e6a7b8c9d0e1f2a3b
9
+ This module only validates *shape* locally; real entitlement/quota checks
10
+ always happen server-side in cloud-service — never trust a client-side
11
+ "valid" result for authorization decisions.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import re
18
+ from dataclasses import dataclass
19
+ from typing import Optional
20
+
21
+ _KEY_PATTERN = re.compile(r"^ZP-(FREE|PRO|ENTERPRISE)-[0-9a-fA-F]{32}$")
22
+
23
+ ENV_VAR = "ZIKRIYON_PRISM_KEY"
24
+
25
+
26
+ class InvalidLicenseKeyError(ValueError):
27
+ pass
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class LicenseKey:
32
+ raw: str
33
+ plan: str
34
+
35
+ def __repr__(self) -> str: # never leak the full key in logs/repr
36
+ return f"LicenseKey(plan={self.plan!r}, key='ZP-{self.plan}-****')"
37
+
38
+
39
+ def load_license(explicit_key: Optional[str] = None) -> Optional[LicenseKey]:
40
+ """
41
+ Resolve a license key from an explicit argument or the
42
+ ZIKRIYON_PRISM_KEY environment variable. Returns None for
43
+ unauthenticated/free-tier usage (local engine only).
44
+ """
45
+ raw = explicit_key or os.environ.get(ENV_VAR)
46
+ if not raw:
47
+ return None
48
+ return parse_license(raw)
49
+
50
+
51
+ def parse_license(raw: str) -> LicenseKey:
52
+ match = _KEY_PATTERN.match(raw.strip())
53
+ if not match:
54
+ raise InvalidLicenseKeyError(
55
+ "License key must match ZP-<FREE|PRO|ENTERPRISE>-<32 hex chars>"
56
+ )
57
+ return LicenseKey(raw=raw.strip(), plan=match.group(1))
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: zikriyon-prism
3
+ Version: 0.1.0
4
+ Summary: Security, moderation and trust scoring engine — local rule engine plus optional cloud ML tier.
5
+ Author: ZEAIPC (Zikr-e-Ameen Innovations & Programming Corporation)
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://zikriyonprism.dev
8
+ Project-URL: Repository, https://github.com/zeaipc/zikriyon-prism
9
+ Keywords: security,moderation,trust-score,spam-detection,anomaly-detection
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Security
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: PyYAML>=6.0
21
+ Requires-Dist: httpx>=0.25
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.4; extra == "dev"
24
+ Requires-Dist: mypy>=1.5; extra == "dev"
25
+ Requires-Dist: ruff>=0.1; extra == "dev"
26
+
27
+ # zikriyon-prism (Python SDK)
28
+
29
+ Security, moderation and trust-scoring engine — local rule engine
30
+ (free, fully offline) plus an optional cloud ML tier (trust score,
31
+ anomaly detection, phishing NLP).
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install zikriyon-prism
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ from zikriyon_prism import RuleEngine
43
+
44
+ engine = RuleEngine.from_file("rules/spam_abuse.yaml")
45
+ result = engine.evaluate(
46
+ {"message": {"text": "click here to unlock your account"}},
47
+ subject_id="user_123",
48
+ )
49
+ print(result.action) # "block"
50
+ ```
51
+
52
+ ## Paid tier
53
+
54
+ ```python
55
+ from zikriyon_prism import CloudClient
56
+
57
+ with CloudClient(license_key="ZP-PRO-<32 hex chars>") as client:
58
+ score = client.get_trust_score("user_123", signals={"login_count": 5})
59
+ ```
60
+
61
+ License keys are validated for *shape* client-side only. Real
62
+ entitlement and quota checks always happen server-side.
63
+
64
+ Full project docs, the Node.js SDK, and the cloud service:
65
+ https://github.com/zeaipc/zikriyon-prism
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ tests/test_engine.py
4
+ zikriyon_prism/__init__.py
5
+ zikriyon_prism/cloud_client.py
6
+ zikriyon_prism/engine.py
7
+ zikriyon_prism/license.py
8
+ zikriyon_prism.egg-info/PKG-INFO
9
+ zikriyon_prism.egg-info/SOURCES.txt
10
+ zikriyon_prism.egg-info/dependency_links.txt
11
+ zikriyon_prism.egg-info/requires.txt
12
+ zikriyon_prism.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+ PyYAML>=6.0
2
+ httpx>=0.25
3
+
4
+ [dev]
5
+ pytest>=7.4
6
+ mypy>=1.5
7
+ ruff>=0.1
@@ -0,0 +1 @@
1
+ zikriyon_prism