sernixa 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.
sernixa-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,378 @@
1
+ Metadata-Version: 2.4
2
+ Name: sernixa
3
+ Version: 0.1.0
4
+ Summary: Sernixa Python SDK
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.28.0
8
+ Provides-Extra: langchain
9
+ Requires-Dist: langchain>=0.1.0; extra == "langchain"
10
+ Provides-Extra: crewai
11
+ Requires-Dist: crewai>=0.80.0; extra == "crewai"
12
+ Provides-Extra: frameworks
13
+ Requires-Dist: langchain>=0.1.0; extra == "frameworks"
14
+ Requires-Dist: crewai>=0.80.0; extra == "frameworks"
15
+
16
+ # Sernixa Python SDK
17
+
18
+ Sernixa gates Python function execution through a deterministic approval engine. The backend decides whether an action is allowed, auto-approved, blocked, or pending human review; the SDK executes the wrapped business function locally only after an approved-style decision.
19
+
20
+ The SDK is intentionally small: it does not execute business logic in the
21
+ Sernixa backend, and it does not pretend to be a full agent framework. It gives
22
+ you low-friction interception points for plain Python, LangChain, CrewAI, and
23
+ MCP-style tool boundaries.
24
+
25
+ ## Quickstart
26
+
27
+ ```bash
28
+ pip install -e packages/sernixa
29
+ export SERNIXA_BASE_URL=http://localhost:8000
30
+ export SERNIXA_API_KEY="$YOUR_LOCAL_DEV_TOKEN"
31
+ ```
32
+
33
+ ```python
34
+ import sernixa
35
+
36
+ @sernixa.intercept(
37
+ intent_id="customer-notes",
38
+ risk_level="LOW",
39
+ operation_class="read",
40
+ data_sensitivity="internal",
41
+ systems_touched=["postgres"],
42
+ metadata={"ticket": "DEMO-1"},
43
+ )
44
+ def summarize_customer(customer_id: str) -> str:
45
+ return f"summary for {customer_id}"
46
+
47
+ print(summarize_customer("cus_123"))
48
+ ```
49
+
50
+ If Sernixa returns `approved`, `auto_approved`, or `executed`, the function runs locally. If it returns `pending_review`, the SDK polls until a terminal decision or timeout.
51
+
52
+ ## Configuration
53
+
54
+ - `SERNIXA_BASE_URL`: Sernixa API URL. Defaults to `http://localhost:8000`.
55
+ - `SERNIXA_API_KEY`: bearer token for the API.
56
+ - `SERNIXA_POLL_INTERVAL_SECONDS`: approval poll interval. Defaults to `2`.
57
+ - `SERNIXA_POLL_TIMEOUT_SECONDS`: approval wait timeout. Defaults to `600`.
58
+ - `SERNIXA_TIMEOUT_SECONDS`: per-request HTTP timeout. Defaults to `10`.
59
+ - `SERNIXA_MAX_RETRIES`: retries for transient network errors, `429`, and `5xx`. Defaults to `2`.
60
+ - `SERNIXA_LOG_LEVEL`: optional SDK logger level such as `INFO` or `DEBUG`.
61
+ - `SERNIXA_DEBUG`: set to `true` for verbose SDK request/decision logging.
62
+
63
+ You can also configure a client directly:
64
+
65
+ ```python
66
+ import os
67
+ from sernixa import Client
68
+
69
+ client = Client(
70
+ base_url="http://localhost:8000",
71
+ api_key=os.environ["SERNIXA_API_KEY"],
72
+ poll_interval=1,
73
+ poll_timeout=120,
74
+ timeout_seconds=10,
75
+ max_retries=2,
76
+ )
77
+ ```
78
+
79
+ ## Intercepting Functions
80
+
81
+ ```python
82
+ @client.intercept(
83
+ intent_id="billing-adjustment",
84
+ risk_level="HIGH",
85
+ operation_class="financial",
86
+ data_sensitivity="financial",
87
+ systems_touched=["stripe", "postgres"],
88
+ )
89
+ def adjust_invoice(invoice_id: str, amount_cents: int) -> None:
90
+ ...
91
+ ```
92
+
93
+ High and critical risk actions are never auto-approved by Sernixa. They remain pending review even when similar low-risk actions have a strong approval history.
94
+
95
+ ## Async Functions
96
+
97
+ The same decorator works on coroutines:
98
+
99
+ ```python
100
+ @client.intercept(
101
+ intent_id="agent-read",
102
+ risk_level="LOW",
103
+ operation_class="read",
104
+ data_sensitivity="internal",
105
+ systems_touched=["vectordb"],
106
+ )
107
+ async def run_agent(query: str) -> str:
108
+ return await agent.ainvoke(query)
109
+ ```
110
+
111
+ ## Decision Handling
112
+
113
+ ```python
114
+ from sernixa import is_approved_status, is_terminal_status
115
+
116
+ assert is_approved_status("auto_approved")
117
+ assert is_terminal_status("rejected")
118
+ ```
119
+
120
+ The SDK raises explicit exceptions for reviewer and policy outcomes:
121
+
122
+ ```python
123
+ from sernixa.exceptions import (
124
+ SernixaBlockedError,
125
+ SernixaConfigurationError,
126
+ SernixaExpiredError,
127
+ SernixaRateLimitError,
128
+ SernixaRejectedError,
129
+ SernixaTimeoutError,
130
+ SernixaValidationError,
131
+ )
132
+
133
+ try:
134
+ adjust_invoice("inv_123", 1000)
135
+ except SernixaBlockedError as exc:
136
+ print(f"Blocked by policy: {exc.reason}")
137
+ except SernixaRejectedError as exc:
138
+ print(f"Rejected by reviewer: {exc.reason}")
139
+ except SernixaExpiredError:
140
+ print("Approval expired before a reviewer decided.")
141
+ except SernixaTimeoutError:
142
+ print("SDK timed out waiting for a decision.")
143
+ ```
144
+
145
+ Configuration and payload mistakes fail before the business function runs:
146
+
147
+ - `SernixaConfigurationError`: invalid base URL, timeout, poll, or retry settings.
148
+ - `SernixaValidationError`: malformed action metadata such as an empty `intent_id`, invalid `risk_level`, or non-JSON metadata.
149
+ - `SernixaRateLimitError`: the backend kept returning `429` after retries.
150
+
151
+ ## Extra Review Metadata
152
+
153
+ Use `metadata` for non-sensitive context that helps the approver understand the action:
154
+
155
+ ```python
156
+ @sernixa.intercept(
157
+ intent_id="support-note",
158
+ risk_level="LOW",
159
+ operation_class="update",
160
+ data_sensitivity="internal",
161
+ systems_touched=["postgres"],
162
+ metadata={"change_ticket": "SUP-1842", "env": "local-demo"},
163
+ )
164
+ def write_support_note(...):
165
+ ...
166
+ ```
167
+
168
+ Core governance fields such as `risk_level` and `idempotency_key` cannot be overridden by extra metadata.
169
+
170
+ ## V3 Delegation Context
171
+
172
+ For local multi-agent workflows, create a delegation token through Sernixa and
173
+ attach it while any delegatee agent runs protected tools:
174
+
175
+ ```python
176
+ from sernixa import Client, delegation_scope, with_delegation
177
+
178
+ client = Client()
179
+ token = client.create_delegation_token(
180
+ delegator_agent_id="orchestrator-agent",
181
+ delegatee_agent_id="worker-agent",
182
+ scope=delegation_scope(
183
+ max_risk_level="low",
184
+ allowed_operation_classes=["read"],
185
+ allowed_data_sensitivities=["internal"],
186
+ resources={"repo": ["sernixa"]},
187
+ ),
188
+ tool_subset=["view-details"],
189
+ )
190
+
191
+ with with_delegation(
192
+ agent_id="worker-agent",
193
+ token_id=token["token_id"],
194
+ signing_secret=os.environ["SERNIXA_REQUEST_SIGNING_SECRET"],
195
+ chain_id=token["chain_id"],
196
+ runtime_id="local-worker-runtime",
197
+ service_identity="spiffe://local/agent/worker-agent",
198
+ ):
199
+ view_details()
200
+ ```
201
+
202
+ The SDK signs each delegated request with a canonical envelope, timestamp,
203
+ nonce, request body hash, key ID, and runtime identity metadata. Sernixa
204
+ verifies the request signature, replay status, token signature, hash chain,
205
+ expiry, delegatee identity, and scope before the existing approval logic runs.
206
+
207
+ ## LangChain Adapter
208
+
209
+ Use the decorator when you control the tool function:
210
+
211
+ ```python
212
+ from langchain.tools import tool
213
+ from sernixa.adapters import langchain_tool
214
+
215
+ @tool
216
+ @langchain_tool(
217
+ intent_id="finance-tool",
218
+ risk_level="HIGH",
219
+ operation_class="financial",
220
+ data_sensitivity="financial",
221
+ systems_touched=["stripe"],
222
+ )
223
+ def transfer_funds_tool(amount_cents: int, destination_account: str) -> str:
224
+ """Transfer funds after Sernixa approval."""
225
+ ...
226
+ ```
227
+
228
+ Use the object proxy when a tool object already exists:
229
+
230
+ ```python
231
+ from sernixa.adapters import secure_langchain_tool
232
+
233
+ protected_tool = secure_langchain_tool(
234
+ existing_tool,
235
+ intent_id="customer-lookup",
236
+ risk_level="LOW",
237
+ operation_class="read",
238
+ data_sensitivity="internal",
239
+ systems_touched=["crm"],
240
+ )
241
+
242
+ result = protected_tool.invoke({"customer_id": "cus_123"})
243
+ ```
244
+
245
+ Install LangChain separately if you use the adapter:
246
+
247
+ ```bash
248
+ pip install "sernixa[langchain]"
249
+ ```
250
+
251
+ The proxy guards `invoke`, `ainvoke`, `run`, `arun`, and direct calls where the
252
+ underlying tool exposes them. It forwards unknown attributes to the wrapped tool.
253
+
254
+ ## CrewAI Adapter
255
+
256
+ Use the decorator for plain functions that become CrewAI tools:
257
+
258
+ ```python
259
+ from sernixa.adapters import crewai_tool
260
+
261
+ @crewai_tool(
262
+ intent_id="ticket-update",
263
+ risk_level="HIGH",
264
+ operation_class="update",
265
+ data_sensitivity="internal",
266
+ systems_touched=["ticketing"],
267
+ )
268
+ def update_ticket(ticket_id: str, note: str) -> str:
269
+ return "updated"
270
+ ```
271
+
272
+ Use the object proxy for existing CrewAI-style tool objects:
273
+
274
+ ```python
275
+ from sernixa.adapters import secure_crewai_tool
276
+
277
+ protected_tool = secure_crewai_tool(
278
+ existing_tool,
279
+ intent_id="crew-ticket-update",
280
+ risk_level="HIGH",
281
+ operation_class="update",
282
+ data_sensitivity="internal",
283
+ systems_touched=["ticketing"],
284
+ )
285
+
286
+ protected_tool.run("SEC-1842")
287
+ ```
288
+
289
+ Install CrewAI separately if you use CrewAI itself:
290
+
291
+ ```bash
292
+ pip install "sernixa[crewai]"
293
+ ```
294
+
295
+ The SDK proxy supports the common `run`, `_run`, and direct-call execution
296
+ shapes without requiring CrewAI as a hard dependency.
297
+
298
+ ## MCP Boundary Helpers
299
+
300
+ Sernixa does not implement the full MCP protocol in this SDK. The real support
301
+ in this pass is an honest boundary wrapper for MCP-style tool dispatch:
302
+
303
+ ```python
304
+ from sernixa.adapters import McpToolBoundary
305
+
306
+ boundary = McpToolBoundary(server_name="workspace-mcp", toolset_id="toolset-prod")
307
+
308
+ def read_file(path: str) -> str:
309
+ return open(path).read()
310
+
311
+ result = boundary.invoke(
312
+ tool_name="read_file",
313
+ arguments={"path": "/workspace/report.md"},
314
+ handler=read_file,
315
+ intent_id="mcp-read-file",
316
+ risk_level="LOW",
317
+ operation_class="read",
318
+ data_sensitivity="internal",
319
+ client_name="local-agent-host",
320
+ )
321
+ ```
322
+
323
+ Place this at the host/router boundary before dispatching a tool call to the
324
+ underlying MCP server/tool implementation. Planned next work is first-class MCP
325
+ server middleware and protocol-aware request/response adapters.
326
+
327
+ ## State Model
328
+
329
+ - `executed`: Sernixa allowed the action and the SDK executed the local function.
330
+ - `auto_approved`: Sernixa policy allowed execution without human review.
331
+ - `pending_review`: the SDK is waiting for a reviewer and polling the approval.
332
+ - `rejected`: a reviewer denied the action; the SDK raises `SernixaRejectedError`.
333
+ - `blocked`: policy/security denied the action; the SDK raises `SernixaBlockedError`.
334
+ - `expired`: approval TTL elapsed; the SDK raises `SernixaExpiredError`.
335
+ - `failed`: backend replay/execution evidence failed; the SDK raises `SernixaError`.
336
+
337
+ ## Troubleshooting
338
+
339
+ - `Action blocked`: inspect `exc.reason` and the approval/audit page. Dangerous primitives and invalid signatures fail closed.
340
+ - `SernixaValidationError`: fix empty IDs, invalid risk levels, empty `systems_touched`, or non-JSON metadata.
341
+ - `SernixaConfigurationError`: check `SERNIXA_BASE_URL`, timeout, poll, and retry values.
342
+ - `SernixaRateLimitError`: reduce agent concurrency or increase backend limits for the environment.
343
+ - `SernixaTimeoutError`: the approval is still pending after `SERNIXA_POLL_TIMEOUT_SECONDS`.
344
+ - Browser works but SDK fails: verify `SERNIXA_API_KEY` and that the backend URL is reachable from the Python process.
345
+
346
+ ## Local And Hosted Configuration
347
+
348
+ Local demo:
349
+
350
+ ```bash
351
+ export SERNIXA_BASE_URL=http://localhost:8000
352
+ export SERNIXA_API_KEY=e2e-admin
353
+ export SERNIXA_POLL_INTERVAL_SECONDS=1
354
+ ```
355
+
356
+ Hosted or shared environment:
357
+
358
+ ```bash
359
+ export SERNIXA_BASE_URL=https://sernixa.example.com
360
+ export SERNIXA_API_KEY="$SERNIXA_SERVICE_TOKEN"
361
+ export SERNIXA_TIMEOUT_SECONDS=10
362
+ export SERNIXA_MAX_RETRIES=2
363
+ ```
364
+
365
+ Use real bearer tokens, HTTPS, shared nonce/rate-limit storage, and production
366
+ KMS/HSM signing before treating a hosted environment as production.
367
+
368
+ Current backend handoff note: local SDK examples target the demo API. A
369
+ production-like Sernixa backend must run with `AUTH_BYPASS=false`, real bearer
370
+ tokens, Redis-backed replay/rate-limit state, and a Postgres-backed audit store.
371
+ The present repo intentionally fails closed in `RUNTIME_MODE=production_like`
372
+ until that Postgres runtime store is implemented.
373
+
374
+ ## Local Examples
375
+
376
+ - `examples/basic_auto_approval/`: local walkthrough showing low-risk, repeated approval memory, and high-risk pending behavior.
377
+ - `examples/multi_agent_delegation/`: generic orchestrator/worker delegation token flow.
378
+ - `examples/sernixa-sdk/`: lower-level sync, async, LangChain, CrewAI, MCP boundary, and compose smoke examples.