novyx 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.
novyx-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: novyx
3
+ Version: 0.1.0
4
+ Summary: Unified Novyx SDK (Core + RAM + Sentinel)
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: requests
7
+ Requires-Dist: pydantic
8
+ Dynamic: requires-dist
9
+ Dynamic: requires-python
10
+ Dynamic: summary
novyx-0.1.0/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Novyx SDK
2
+
3
+ Unified SDK for Novyx Core + RAM + Sentinel.
4
+
5
+ ## Install (local)
6
+
7
+ ```bash
8
+ pip install -e ./sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from novyx import Novyx
15
+
16
+ nx = Novyx(
17
+ api_key="nram_...",
18
+ api_url="https://novyx-ram-api.fly.dev",
19
+ sentinel_url="https://novyx-sentinel.fly.dev",
20
+ strict=False,
21
+ )
22
+
23
+ result = nx.remember("User prefers dark mode", tags=["ui", "preference"])
24
+ memories = nx.recall("UI preferences", limit=5)
25
+ nx.act("send_email", {"to": "user@example.com"})
26
+ nx.rollback("2025-01-30T14:00:00Z")
27
+ ```
28
+
29
+ ## Four Verbs
30
+
31
+ - `remember(content, tags=[])` → store memory with cryptographic hash
32
+ - `recall(query, limit=5)` → semantic search (uses `q` param)
33
+ - `act(action_name, params)` → Sentinel policy check before action
34
+ - `rollback(target)` → Magic Rollback™ (timestamp or artifact id)
35
+
36
+ ## Enterprise Telemetry
37
+
38
+ Set `NOVYX_TIER=enterprise` to enable audit pulse logging to the Core audit trail.
39
+
40
+ ## Test Drive
41
+
42
+ ```bash
43
+ python sdk/test_drive.py
44
+ ```
@@ -0,0 +1,9 @@
1
+ from .client import Novyx
2
+ from .exceptions import NovyxAuthError, NovyxSecurityError, NovyxStorageError
3
+
4
+ __all__ = [
5
+ "Novyx",
6
+ "NovyxAuthError",
7
+ "NovyxSecurityError",
8
+ "NovyxStorageError",
9
+ ]
@@ -0,0 +1,20 @@
1
+ from extensions.novyx_ram.sdk import NovyxRAMClient, AuthenticationError, NovyxRAMError
2
+
3
+ from .exceptions import NovyxAuthError, NovyxStorageError
4
+
5
+
6
+ def validate_api_key(api_key: str, api_url: str) -> None:
7
+ """
8
+ Validate RAM API key by calling an authenticated endpoint.
9
+
10
+ Raises:
11
+ NovyxAuthError: If the API key is invalid.
12
+ NovyxStorageError: If the API is unreachable.
13
+ """
14
+ try:
15
+ client = NovyxRAMClient(api_key=api_key, base_url=api_url)
16
+ _ = client.stats()
17
+ except AuthenticationError as exc:
18
+ raise NovyxAuthError("Invalid Novyx API key.") from exc
19
+ except NovyxRAMError as exc:
20
+ raise NovyxStorageError(str(exc)) from exc
@@ -0,0 +1,261 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+ import uuid
5
+ from datetime import datetime, timezone
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import requests
9
+
10
+ from extensions.novyx_ram.sdk import (
11
+ NovyxRAMClient,
12
+ NovyxRAMError,
13
+ AuthenticationError,
14
+ )
15
+ from sentinel.circuit_breaker import PolicyEvaluator, EvaluationMode
16
+ from sentinel.trace_models import SentinelTraceStep, StepType, PolicyStatus
17
+ from sentinel.rollback_analyzer import RollbackAnalyzer
18
+ from sentinel.surgical_rollback import SurgicalRollback
19
+ from security.forensic_rollback import ForensicRollback
20
+
21
+ from .auth import validate_api_key
22
+ from .exceptions import NovyxAuthError, NovyxSecurityError, NovyxStorageError
23
+ from .telemetry import TelemetryLogger
24
+
25
+
26
+ class Novyx:
27
+ """
28
+ Unified SDK interface for Novyx Core + RAM + Sentinel.
29
+
30
+ Four verbs:
31
+ - remember
32
+ - recall
33
+ - act
34
+ - rollback
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ api_key: str,
40
+ api_url: str = "https://novyx-ram-api.fly.dev",
41
+ sentinel_url: str = "https://novyx-sentinel.fly.dev",
42
+ session_id: Optional[str] = None,
43
+ strict: bool = False,
44
+ ):
45
+ self.api_key = api_key
46
+ self.api_url = api_url.rstrip("/")
47
+ self.sentinel_url = sentinel_url.rstrip("/")
48
+ self.session_id = session_id or f"session_{uuid.uuid4()}"
49
+ self.strict = strict
50
+
51
+ validate_api_key(api_key=self.api_key, api_url=self.api_url)
52
+
53
+ try:
54
+ self.ram = NovyxRAMClient(api_key=self.api_key, base_url=self.api_url)
55
+ except AuthenticationError as exc:
56
+ raise NovyxAuthError("Invalid Novyx API key.") from exc
57
+ except NovyxRAMError as exc:
58
+ raise NovyxStorageError(str(exc)) from exc
59
+
60
+ self.telemetry = TelemetryLogger(session_id=self.session_id)
61
+ self.policy_evaluator = PolicyEvaluator(mode=EvaluationMode.ENFORCEMENT)
62
+
63
+ self._memory_count = 0
64
+ self._action_count = 0
65
+
66
+ def _now_iso(self) -> str:
67
+ return datetime.now(timezone.utc).isoformat()
68
+
69
+ def _hash_content(self, content: str, tags: List[str]) -> str:
70
+ payload = {
71
+ "content": content,
72
+ "tags": tags,
73
+ "session_id": self.session_id,
74
+ }
75
+ digest = hashlib.sha256(
76
+ json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
77
+ ).hexdigest()
78
+ return f"urn:sha256:{digest}"
79
+
80
+ def _forge_debt_warning(self) -> None:
81
+ if not self.strict:
82
+ return
83
+
84
+ if self._action_count == 0:
85
+ ratio = f"{self._memory_count}:0"
86
+ else:
87
+ ratio = f"{self._memory_count}:{self._action_count}"
88
+
89
+ print(
90
+ "Warning: "
91
+ f"{self._memory_count} memories stored, "
92
+ f"{self._action_count} actions taken. "
93
+ f"Consumption debt ratio: {ratio}"
94
+ )
95
+
96
+ def remember(self, content: str, tags: Optional[List[str]] = None) -> Dict[str, Any]:
97
+ tags = tags or []
98
+ content_hash = self._hash_content(content, tags)
99
+
100
+ try:
101
+ result = self.ram.store(
102
+ observation=content,
103
+ tags=tags,
104
+ context=f"content_hash={content_hash}",
105
+ agent_id=self.session_id,
106
+ )
107
+ except NovyxRAMError as exc:
108
+ raise NovyxStorageError(str(exc)) from exc
109
+
110
+ self._memory_count += 1
111
+ self.telemetry.log_pulse(
112
+ "remember",
113
+ {
114
+ "session_id": self.session_id,
115
+ "memory_id": result.uuid,
116
+ "hash": content_hash,
117
+ "tags": tags,
118
+ },
119
+ )
120
+ self._forge_debt_warning()
121
+
122
+ return {
123
+ "id": result.uuid,
124
+ "hash": content_hash,
125
+ "stored_at": self._now_iso(),
126
+ }
127
+
128
+ def recall(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
129
+ try:
130
+ search_result = self.ram.search(query=query, limit=limit)
131
+ except NovyxRAMError as exc:
132
+ raise NovyxStorageError(str(exc)) from exc
133
+
134
+ retrieved_at = self._now_iso()
135
+ cleaned = []
136
+ for mem in search_result.memories:
137
+ cleaned.append(
138
+ {
139
+ "uuid": mem.uuid,
140
+ "observation": mem.observation,
141
+ "context": mem.context,
142
+ "tags": mem.tags,
143
+ "agent_id": mem.agent_id,
144
+ "created_at": mem.created_at,
145
+ "score": mem.score,
146
+ "query": query,
147
+ "retrieved_at": retrieved_at,
148
+ "session_id": self.session_id,
149
+ }
150
+ )
151
+
152
+ self.telemetry.log_pulse(
153
+ "recall",
154
+ {
155
+ "session_id": self.session_id,
156
+ "query": query,
157
+ "limit": limit,
158
+ "results": len(cleaned),
159
+ },
160
+ )
161
+ return cleaned
162
+
163
+ def act(self, action_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
164
+ self._check_sentinel()
165
+ content = json.dumps({"action": action_name, "params": params}, sort_keys=True)
166
+ integrity_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
167
+ chain_hash = hashlib.sha256(
168
+ (integrity_hash + ("0" * 64)).encode("utf-8")
169
+ ).hexdigest()
170
+
171
+ step = SentinelTraceStep(
172
+ trace_id=f"urn:uuid:{self.session_id}",
173
+ step_index=self._action_count,
174
+ step_type=StepType.ACTION,
175
+ content=content,
176
+ policy_status=PolicyStatus.SKIPPED,
177
+ integrity_hash=integrity_hash,
178
+ integrity_chain_hash=chain_hash,
179
+ )
180
+
181
+ evaluation = self.policy_evaluator.evaluate_step(step)
182
+ if evaluation.policy_status == PolicyStatus.FAILED:
183
+ violation = evaluation.violations[0]
184
+ raise NovyxSecurityError(
185
+ f"{violation.policy_name}: {violation.reason}"
186
+ )
187
+
188
+ self._action_count += 1
189
+ self.telemetry.log_pulse(
190
+ "act",
191
+ {
192
+ "session_id": self.session_id,
193
+ "action": action_name,
194
+ "params": params,
195
+ "policy_status": evaluation.policy_status.value,
196
+ "risk_score": evaluation.risk_score,
197
+ },
198
+ )
199
+
200
+ return {
201
+ "action": action_name,
202
+ "status": "approved",
203
+ "policy_status": evaluation.policy_status.value,
204
+ "risk_score": evaluation.risk_score,
205
+ }
206
+
207
+ def _check_sentinel(self) -> None:
208
+ try:
209
+ resp = requests.get(f"{self.sentinel_url}/health", timeout=5)
210
+ except requests.RequestException as exc:
211
+ raise NovyxSecurityError("Sentinel is unreachable.") from exc
212
+
213
+ if resp.status_code != 200:
214
+ raise NovyxSecurityError(
215
+ f"Sentinel integrity check failed (status {resp.status_code})."
216
+ )
217
+
218
+ def rollback(self, target: str) -> Dict[str, Any]:
219
+ memory_dir = os.getenv("NOVYX_MEMORY_DIR", "memory")
220
+ analyzer = RollbackAnalyzer(memory_dir=memory_dir)
221
+ rollback_engine = ForensicRollback(memory_dir=memory_dir)
222
+ surgical = SurgicalRollback(memory_dir=memory_dir, rollback=rollback_engine)
223
+
224
+ suggestion = None
225
+ if "T" in target and "Z" in target:
226
+ impact = analyzer.calculate_impact(target)
227
+ result = rollback_engine.execute_rollback(target)
228
+ suggestion = impact.get("suggestion") or "Rollback executed"
229
+ rolled_back = result.get("artifacts_restored", [])
230
+ preserved = result.get("artifacts_quarantined", [])
231
+ else:
232
+ # Treat as artifact_id; rollback to latest green point with dependencies
233
+ points = rollback_engine.list_rollback_points()
234
+ if not points:
235
+ raise NovyxStorageError("No rollback points available.")
236
+ latest = points[-1]
237
+ impact = analyzer.calculate_impact(latest.timestamp)
238
+ suggestion = impact.get("suggestion") or "Surgical rollback executed"
239
+ result = surgical.rollback_artifacts(
240
+ [target],
241
+ latest.timestamp,
242
+ include_dependencies=True,
243
+ dry_run=False,
244
+ ).to_dict()
245
+ rolled_back = result.get("artifacts_rolled_back", [])
246
+ preserved = result.get("artifacts_failed", [])
247
+
248
+ self.telemetry.log_pulse(
249
+ "rollback",
250
+ {
251
+ "session_id": self.session_id,
252
+ "target": target,
253
+ "suggestion": suggestion,
254
+ },
255
+ )
256
+
257
+ return {
258
+ "rolled_back": rolled_back,
259
+ "preserved": preserved,
260
+ "suggestion": suggestion,
261
+ }
@@ -0,0 +1,10 @@
1
+ class NovyxAuthError(Exception):
2
+ """Raised when API key authentication fails."""
3
+
4
+
5
+ class NovyxSecurityError(Exception):
6
+ """Raised when Sentinel blocks an action or integrity check fails."""
7
+
8
+
9
+ class NovyxStorageError(Exception):
10
+ """Raised when RAM or Core storage operations fail."""
@@ -0,0 +1,56 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Dict, Any, Union, Optional
7
+
8
+ from security.audit_trail import AuditTrail, OperationType
9
+
10
+ from .exceptions import NovyxStorageError
11
+
12
+
13
+ class TelemetryLogger:
14
+ """
15
+ Enterprise-only audit logging for SDK usage.
16
+
17
+ Logs a Pulse event for each SDK call when NOVYX_TIER=enterprise.
18
+ """
19
+
20
+ def __init__(self, session_id: str, memory_dir: Optional[Union[str, Path]] = None):
21
+ self.session_id = session_id
22
+ self.tier = os.getenv("NOVYX_TIER", "").lower()
23
+ self.memory_dir = Path(memory_dir or os.getenv("NOVYX_MEMORY_DIR", "memory"))
24
+ self.audit_dir = self.memory_dir / ".audit"
25
+ self._audit = None
26
+
27
+ if self.tier == "enterprise":
28
+ try:
29
+ self._audit = AuditTrail(self.audit_dir, verify_on_load=False)
30
+ except Exception as exc:
31
+ raise NovyxStorageError(f"Failed to initialize audit trail: {exc}") from exc
32
+
33
+ def _hash_payload(self, payload: Dict[str, Any]) -> str:
34
+ content = json.dumps(payload, sort_keys=True, separators=(",", ":"))
35
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
36
+
37
+ def log_pulse(self, verb: str, payload: Dict[str, Any]) -> None:
38
+ if self.tier != "enterprise":
39
+ return
40
+
41
+ if not self._audit:
42
+ raise NovyxStorageError("Audit trail not initialized.")
43
+
44
+ content_hash = self._hash_payload(payload)
45
+ timestamp = datetime.now(timezone.utc).isoformat()
46
+ self._audit.log(
47
+ operation=OperationType.ACCESS.value,
48
+ agent_id=self.session_id,
49
+ artifact_id=f"pulse:{verb}",
50
+ content_hash=content_hash,
51
+ metadata={
52
+ "verb": verb,
53
+ "timestamp": timestamp,
54
+ "payload": payload,
55
+ },
56
+ )
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: novyx
3
+ Version: 0.1.0
4
+ Summary: Unified Novyx SDK (Core + RAM + Sentinel)
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: requests
7
+ Requires-Dist: pydantic
8
+ Dynamic: requires-dist
9
+ Dynamic: requires-python
10
+ Dynamic: summary
@@ -0,0 +1,17 @@
1
+ README.md
2
+ setup.py
3
+ ./novyx/__init__.py
4
+ ./novyx/auth.py
5
+ ./novyx/client.py
6
+ ./novyx/exceptions.py
7
+ ./novyx/telemetry.py
8
+ novyx/__init__.py
9
+ novyx/auth.py
10
+ novyx/client.py
11
+ novyx/exceptions.py
12
+ novyx/telemetry.py
13
+ novyx.egg-info/PKG-INFO
14
+ novyx.egg-info/SOURCES.txt
15
+ novyx.egg-info/dependency_links.txt
16
+ novyx.egg-info/requires.txt
17
+ novyx.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests
2
+ pydantic
@@ -0,0 +1 @@
1
+ novyx
novyx-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
novyx-0.1.0/setup.py ADDED
@@ -0,0 +1,15 @@
1
+ from setuptools import setup, find_packages
2
+
3
+
4
+ setup(
5
+ name="novyx",
6
+ version="0.1.0",
7
+ description="Unified Novyx SDK (Core + RAM + Sentinel)",
8
+ package_dir={"": "."},
9
+ packages=find_packages("."),
10
+ install_requires=[
11
+ "requests",
12
+ "pydantic",
13
+ ],
14
+ python_requires=">=3.9",
15
+ )