xproof 0.2.7__tar.gz → 0.2.8__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.
- {xproof-0.2.7 → xproof-0.2.8}/LICENSE +1 -1
- {xproof-0.2.7 → xproof-0.2.8}/PKG-INFO +184 -32
- {xproof-0.2.7 → xproof-0.2.8}/README.md +182 -30
- {xproof-0.2.7 → xproof-0.2.8}/pyproject.toml +3 -2
- xproof-0.2.8/tests/test_client.py +1377 -0
- xproof-0.2.8/tests/test_compliance_observability_example.py +54 -0
- {xproof-0.2.7 → xproof-0.2.8}/tests/test_confidence_trail.py +21 -10
- {xproof-0.2.7 → xproof-0.2.8}/tests/test_context_drift.py +1 -0
- xproof-0.2.8/tests/test_integration_policy_check.py +104 -0
- xproof-0.2.8/tests/test_langchain_callback_demo.py +210 -0
- xproof-0.2.8/tests/test_langchain_certify_tool_demo.py +113 -0
- {xproof-0.2.7 → xproof-0.2.8}/tests/test_langchain_tool.py +38 -15
- {xproof-0.2.7 → xproof-0.2.8}/tests/test_policy_check.py +33 -22
- xproof-0.2.8/tests/test_version.py +45 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof/__init__.py +13 -2
- {xproof-0.2.7 → xproof-0.2.8}/xproof/client.py +111 -30
- {xproof-0.2.7 → xproof-0.2.8}/xproof/exceptions.py +15 -3
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/__init__.py +11 -9
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/autogen.py +5 -5
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/crewai.py +27 -21
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/deerflow.py +8 -6
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/fetchai.py +67 -16
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/langchain.py +24 -18
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/llamaindex.py +30 -23
- {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/openai_agents.py +35 -37
- {xproof-0.2.7 → xproof-0.2.8}/xproof/langchain_tool.py +10 -16
- {xproof-0.2.7 → xproof-0.2.8}/xproof/models.py +145 -48
- {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/PKG-INFO +184 -32
- {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/SOURCES.txt +5 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/requires.txt +1 -1
- xproof-0.2.7/tests/test_client.py +0 -637
- {xproof-0.2.7 → xproof-0.2.8}/setup.cfg +0 -0
- {xproof-0.2.7 → xproof-0.2.8}/tests/test_integration.py +0 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof/py.typed +0 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof/utils.py +0 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/dependency_links.txt +0 -0
- {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xproof
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX
|
|
5
5
|
Author-email: xProof <contact@xproof.app>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -34,7 +34,7 @@ Requires-Dist: mypy>=1.0; extra == "dev"
|
|
|
34
34
|
Requires-Dist: types-requests>=2.25.0; extra == "dev"
|
|
35
35
|
Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
36
36
|
Provides-Extra: llamaindex
|
|
37
|
-
Requires-Dist: llama-index-core>=0.
|
|
37
|
+
Requires-Dist: llama-index-core>=0.14.20; extra == "llamaindex"
|
|
38
38
|
Provides-Extra: autogen
|
|
39
39
|
Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
|
|
40
40
|
Provides-Extra: openai-agents
|
|
@@ -271,6 +271,116 @@ else:
|
|
|
271
271
|
|
|
272
272
|
---
|
|
273
273
|
|
|
274
|
+
## Context Drift Detection
|
|
275
|
+
|
|
276
|
+
Detect when the execution context of an agent changed between proof stages — different model version, tool set, strategy, or operator scope:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from xproof import XProofClient
|
|
280
|
+
|
|
281
|
+
client = XProofClient(api_key="pm_your_key")
|
|
282
|
+
|
|
283
|
+
drift = client.get_context_drift("trade-xyz-2026")
|
|
284
|
+
|
|
285
|
+
if drift.context_coherent:
|
|
286
|
+
print(f"No drift detected (score={drift.drift_score:.2f})")
|
|
287
|
+
else:
|
|
288
|
+
print(f"Drift detected! score={drift.drift_score:.2f}")
|
|
289
|
+
print(f" Drifted fields : {drift.fields_drifted}")
|
|
290
|
+
print(f" Stable fields : {drift.fields_stable}")
|
|
291
|
+
print(f" Absent fields : {drift.fields_absent}")
|
|
292
|
+
|
|
293
|
+
for i, stage in enumerate(drift.stages):
|
|
294
|
+
print(f" Stage {i}: model={stage.model_hash}, tools={stage.tools_version}")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`get_context_drift()` returns a `ContextDrift` object with these fields:
|
|
298
|
+
|
|
299
|
+
| Field | Type | Description |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `context_coherent` | `bool` | `True` if no drift detected across the chain |
|
|
302
|
+
| `drift_score` | `float` | `0.0` = fully coherent · `1.0` = total drift |
|
|
303
|
+
| `fields_drifted` | `list[str]` | Fields that changed at least once |
|
|
304
|
+
| `fields_stable` | `list[str]` | Fields present in all stages and unchanged |
|
|
305
|
+
| `fields_absent` | `list[str]` | Fields never populated in any stage |
|
|
306
|
+
| `stages` | `list[ContextDriftStage]` | Per-stage snapshots (`model_hash`, `tools_version`, `strategy_snapshot`, `operator_scope`) |
|
|
307
|
+
| `raw` | `dict` | Unmodified API response |
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Timing Breakdown
|
|
312
|
+
|
|
313
|
+
Anchor the full decision chronology on-chain so forensic auditors can distinguish real-time reasoning from post-hoc reconstruction. Pass a `TimingBreakdown` dict to `certify_with_confidence()`:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from xproof import XProofClient, TimingBreakdown
|
|
317
|
+
import time
|
|
318
|
+
|
|
319
|
+
client = XProofClient(api_key="pm_your_key")
|
|
320
|
+
|
|
321
|
+
# Capture timestamps at each lifecycle event
|
|
322
|
+
instruction_ts = "2026-04-20T14:30:00Z" # when the agent received the task
|
|
323
|
+
reasoning_ts = "2026-04-20T14:30:01Z" # when reasoning/planning started
|
|
324
|
+
action_ts = "2026-04-20T14:30:05Z" # when the action was executed
|
|
325
|
+
|
|
326
|
+
timing: TimingBreakdown = {
|
|
327
|
+
"instruction_received_at": instruction_ts,
|
|
328
|
+
"reasoning_started_at": reasoning_ts,
|
|
329
|
+
"action_taken_at": action_ts,
|
|
330
|
+
"jurisdiction_type": "autonomous_inference", # legal accountability class
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
cert = client.certify_with_confidence(
|
|
334
|
+
file_hash="e3b0c44298fc1c149afb...",
|
|
335
|
+
file_name="trade-decision.json",
|
|
336
|
+
author="trading-agent",
|
|
337
|
+
confidence_level=0.97,
|
|
338
|
+
threshold_stage="pre-commitment",
|
|
339
|
+
decision_id="trade-xyz-2026",
|
|
340
|
+
reversibility_class="irreversible",
|
|
341
|
+
timing=timing,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# The server echoes timing_breakdown with computed durations
|
|
345
|
+
tb = cert.timing_breakdown
|
|
346
|
+
if tb:
|
|
347
|
+
print(f"Thinking time : {tb.get('reasoning_duration_ms')} ms")
|
|
348
|
+
print(f"Total latency : {tb.get('total_duration_ms')} ms")
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### jurisdiction_type
|
|
352
|
+
|
|
353
|
+
The `jurisdiction_type` field records the legal accountability classification for the decision:
|
|
354
|
+
|
|
355
|
+
| Value | Meaning |
|
|
356
|
+
|---|---|
|
|
357
|
+
| `instruction_following` | Agent executed an explicit human instruction — accountability follows the principal |
|
|
358
|
+
| `autonomous_inference` | Agent reached its own conclusion — agent and its operator bear primary accountability |
|
|
359
|
+
| `human_approved` | Agent recommended; a human explicitly approved — shared accountability |
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from xproof import JURISDICTION_TYPES
|
|
363
|
+
|
|
364
|
+
print(JURISDICTION_TYPES)
|
|
365
|
+
# ('instruction_following', 'autonomous_inference', 'human_approved')
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Reading timing_breakdown from a Certification
|
|
369
|
+
|
|
370
|
+
When the server echoes the timestamps it also computes:
|
|
371
|
+
- `reasoning_duration_ms` — milliseconds between `reasoning_started_at` and `action_taken_at`
|
|
372
|
+
- `total_duration_ms` — milliseconds between `instruction_received_at` and `action_taken_at`
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
tb = cert.timing_breakdown # TimingBreakdown | None
|
|
376
|
+
if tb is not None:
|
|
377
|
+
print(tb["instruction_received_at"]) # "2026-04-20T14:30:00Z"
|
|
378
|
+
print(tb["reasoning_duration_ms"]) # 4000
|
|
379
|
+
print(tb["total_duration_ms"]) # 5000
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
274
384
|
## Governance & Policy Enforcement
|
|
275
385
|
|
|
276
386
|
xProof detects automatically when an agent acted with insufficient confidence on an irreversible action — and writes the evidence on-chain before you ever open an incident report.
|
|
@@ -311,6 +421,8 @@ if not check.policy_compliant:
|
|
|
311
421
|
# → confidence 0.72 is below the required threshold of 0.95
|
|
312
422
|
```
|
|
313
423
|
|
|
424
|
+
Prefer `get_policy_check()` when you only need a fast governance gate. It avoids fetching the full confidence trail, which is better reserved for post-incident review or detailed forensic analysis.
|
|
425
|
+
|
|
314
426
|
### Full confidence trail with policy result
|
|
315
427
|
|
|
316
428
|
```python
|
|
@@ -342,11 +454,9 @@ from xproof.exceptions import PolicyViolationError
|
|
|
342
454
|
certify = XProofCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
343
455
|
|
|
344
456
|
decision = {
|
|
345
|
-
"action": "
|
|
346
|
-
"scope": "
|
|
347
|
-
"
|
|
348
|
-
"retention_policy_checked": True,
|
|
349
|
-
"legal_hold_clear": True,
|
|
457
|
+
"action": "delete_pii_records",
|
|
458
|
+
"scope": "eu-region",
|
|
459
|
+
"count": 15_000,
|
|
350
460
|
}
|
|
351
461
|
decision_id = "del-run-2026-04-20"
|
|
352
462
|
|
|
@@ -357,10 +467,10 @@ try:
|
|
|
357
467
|
"threshold_stage": "pre-commitment",
|
|
358
468
|
"decision_id": decision_id,
|
|
359
469
|
"reversibility_class": "irreversible",
|
|
360
|
-
"why": "Scheduled GDPR
|
|
470
|
+
"why": "Scheduled GDPR retention cleanup",
|
|
361
471
|
})
|
|
362
472
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
363
|
-
#
|
|
473
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
364
474
|
except PolicyViolationError as exc:
|
|
365
475
|
for v in exc.violations:
|
|
366
476
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -392,7 +502,7 @@ tx_hash = await certify.arun({
|
|
|
392
502
|
"threshold_stage": "pre-commitment",
|
|
393
503
|
"decision_id": decision_id,
|
|
394
504
|
"reversibility_class": "irreversible",
|
|
395
|
-
"why": "Scheduled GDPR
|
|
505
|
+
"why": "Scheduled GDPR retention cleanup",
|
|
396
506
|
})
|
|
397
507
|
```
|
|
398
508
|
|
|
@@ -425,11 +535,9 @@ from xproof.exceptions import PolicyViolationError
|
|
|
425
535
|
certify = XProofCrewCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
426
536
|
|
|
427
537
|
decision = {
|
|
428
|
-
"action": "
|
|
429
|
-
"scope": "
|
|
430
|
-
"
|
|
431
|
-
"retention_policy_checked": True,
|
|
432
|
-
"legal_hold_clear": True,
|
|
538
|
+
"action": "delete_pii_records",
|
|
539
|
+
"scope": "eu-region",
|
|
540
|
+
"count": 15_000,
|
|
433
541
|
}
|
|
434
542
|
decision_id = "del-run-2026-04-20"
|
|
435
543
|
|
|
@@ -440,10 +548,10 @@ try:
|
|
|
440
548
|
threshold_stage="pre-commitment",
|
|
441
549
|
decision_id=decision_id,
|
|
442
550
|
reversibility_class="irreversible",
|
|
443
|
-
why="Scheduled GDPR
|
|
551
|
+
why="Scheduled GDPR retention cleanup",
|
|
444
552
|
)
|
|
445
553
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
446
|
-
#
|
|
554
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
447
555
|
except PolicyViolationError as exc:
|
|
448
556
|
for v in exc.violations:
|
|
449
557
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -474,11 +582,9 @@ from xproof.integrations.autogen import xproof_certify_decision
|
|
|
474
582
|
from xproof.exceptions import PolicyViolationError
|
|
475
583
|
|
|
476
584
|
decision = {
|
|
477
|
-
"action": "
|
|
478
|
-
"scope": "
|
|
479
|
-
"
|
|
480
|
-
"retention_policy_checked": True,
|
|
481
|
-
"legal_hold_clear": True,
|
|
585
|
+
"action": "delete_pii_records",
|
|
586
|
+
"scope": "eu-region",
|
|
587
|
+
"count": 15_000,
|
|
482
588
|
}
|
|
483
589
|
decision_id = "del-run-2026-04-20"
|
|
484
590
|
|
|
@@ -489,12 +595,12 @@ try:
|
|
|
489
595
|
threshold_stage="pre-commitment",
|
|
490
596
|
decision_id=decision_id,
|
|
491
597
|
reversibility_class="irreversible",
|
|
492
|
-
why="Scheduled GDPR
|
|
598
|
+
why="Scheduled GDPR retention cleanup",
|
|
493
599
|
author="data-hygiene-agent",
|
|
494
600
|
api_key="pm_...",
|
|
495
601
|
)
|
|
496
602
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
497
|
-
#
|
|
603
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
498
604
|
except PolicyViolationError as exc:
|
|
499
605
|
for v in exc.violations:
|
|
500
606
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -535,13 +641,9 @@ def hash_string(s: str) -> str:
|
|
|
535
641
|
# chain-of-thought or tool-call output produced just before execution.)
|
|
536
642
|
|
|
537
643
|
decision = {
|
|
538
|
-
"action": "
|
|
539
|
-
"scope": "
|
|
540
|
-
"
|
|
541
|
-
"retention_policy_checked": True,
|
|
542
|
-
"legal_hold_clear": True,
|
|
543
|
-
"agent": "data-hygiene-agent",
|
|
544
|
-
"run_id": "del-run-2026-04-20",
|
|
644
|
+
"action": "delete_pii_records",
|
|
645
|
+
"scope": "eu-region",
|
|
646
|
+
"count": 15_000,
|
|
545
647
|
}
|
|
546
648
|
decision_id = "del-run-2026-04-20"
|
|
547
649
|
reasoning_hash = hash_string(json.dumps(decision, sort_keys=True))
|
|
@@ -577,7 +679,7 @@ if not check.policy_compliant:
|
|
|
577
679
|
|
|
578
680
|
# ── Step 4: Execute only when compliant ──────────────────────────────────────
|
|
579
681
|
print(f"Policy compliant — proceeding with deletion (cert: {cert.transaction_hash})")
|
|
580
|
-
#
|
|
682
|
+
# delete_pii_records(decision["scope"]) # your actual execution here
|
|
581
683
|
```
|
|
582
684
|
|
|
583
685
|
**What happens if the agent's confidence is too low?**
|
|
@@ -686,6 +788,56 @@ shippers (Fluentd, the Datadog Agent, the CloudWatch agent) forward verbatim.
|
|
|
686
788
|
Create a log-based metric or alert on `event = "policy_violation"` to get
|
|
687
789
|
dashboard counts and threshold alerts with no extra instrumentation.
|
|
688
790
|
|
|
791
|
+
> **Runnable example** — `python-sdk/examples/compliance_observability.py` runs the full pattern with a mock client and verifies structured output. No API key needed.
|
|
792
|
+
|
|
793
|
+
#### Drop-in: CrewAI one-liner
|
|
794
|
+
|
|
795
|
+
`XProofCrewCertifyTool` wraps `certify_with_confidence` + `get_policy_check` into a single `run()` call. Replace the manual four-step loop with:
|
|
796
|
+
|
|
797
|
+
```python
|
|
798
|
+
from xproof.integrations.crewai import XProofCrewCertifyTool
|
|
799
|
+
from xproof.exceptions import PolicyViolationError
|
|
800
|
+
|
|
801
|
+
certify = XProofCrewCertifyTool(api_key="pm_...", author="compliance-agent")
|
|
802
|
+
|
|
803
|
+
try:
|
|
804
|
+
result = certify.run(
|
|
805
|
+
decision_text=json.dumps(decision),
|
|
806
|
+
confidence_level=0.91,
|
|
807
|
+
reversibility_class="irreversible",
|
|
808
|
+
decision_id="trade-xyz-2026",
|
|
809
|
+
)
|
|
810
|
+
except PolicyViolationError as exc:
|
|
811
|
+
for v in exc.violations:
|
|
812
|
+
_emit_violation("trade-xyz-2026", v) # reuse _emit_violation from above
|
|
813
|
+
raise RuntimeError("Action aborted: policy compliance check failed.") from exc
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
#### Drop-in: AutoGen one-liner
|
|
817
|
+
|
|
818
|
+
`xproof_certify_decision` is a plain callable — register it as a function tool on any `ConversableAgent` or call it directly:
|
|
819
|
+
|
|
820
|
+
```python
|
|
821
|
+
from xproof.integrations.autogen import xproof_certify_decision
|
|
822
|
+
from xproof.exceptions import PolicyViolationError
|
|
823
|
+
|
|
824
|
+
try:
|
|
825
|
+
result = xproof_certify_decision(
|
|
826
|
+
api_key="pm_...",
|
|
827
|
+
decision_text=json.dumps(decision),
|
|
828
|
+
confidence_level=0.91,
|
|
829
|
+
reversibility_class="irreversible",
|
|
830
|
+
decision_id="trade-xyz-2026",
|
|
831
|
+
author="autogen-agent",
|
|
832
|
+
)
|
|
833
|
+
except PolicyViolationError as exc:
|
|
834
|
+
for v in exc.violations:
|
|
835
|
+
_emit_violation("trade-xyz-2026", v)
|
|
836
|
+
raise RuntimeError("Action aborted: policy compliance check failed.") from exc
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Both tools write the violation evidence on-chain before raising — so the structured log and the chain anchor are always in sync.
|
|
840
|
+
|
|
689
841
|
### Three classes, one parameter
|
|
690
842
|
|
|
691
843
|
| `reversibility_class` | What it means | Policy threshold |
|
|
@@ -225,6 +225,116 @@ else:
|
|
|
225
225
|
|
|
226
226
|
---
|
|
227
227
|
|
|
228
|
+
## Context Drift Detection
|
|
229
|
+
|
|
230
|
+
Detect when the execution context of an agent changed between proof stages — different model version, tool set, strategy, or operator scope:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from xproof import XProofClient
|
|
234
|
+
|
|
235
|
+
client = XProofClient(api_key="pm_your_key")
|
|
236
|
+
|
|
237
|
+
drift = client.get_context_drift("trade-xyz-2026")
|
|
238
|
+
|
|
239
|
+
if drift.context_coherent:
|
|
240
|
+
print(f"No drift detected (score={drift.drift_score:.2f})")
|
|
241
|
+
else:
|
|
242
|
+
print(f"Drift detected! score={drift.drift_score:.2f}")
|
|
243
|
+
print(f" Drifted fields : {drift.fields_drifted}")
|
|
244
|
+
print(f" Stable fields : {drift.fields_stable}")
|
|
245
|
+
print(f" Absent fields : {drift.fields_absent}")
|
|
246
|
+
|
|
247
|
+
for i, stage in enumerate(drift.stages):
|
|
248
|
+
print(f" Stage {i}: model={stage.model_hash}, tools={stage.tools_version}")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`get_context_drift()` returns a `ContextDrift` object with these fields:
|
|
252
|
+
|
|
253
|
+
| Field | Type | Description |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `context_coherent` | `bool` | `True` if no drift detected across the chain |
|
|
256
|
+
| `drift_score` | `float` | `0.0` = fully coherent · `1.0` = total drift |
|
|
257
|
+
| `fields_drifted` | `list[str]` | Fields that changed at least once |
|
|
258
|
+
| `fields_stable` | `list[str]` | Fields present in all stages and unchanged |
|
|
259
|
+
| `fields_absent` | `list[str]` | Fields never populated in any stage |
|
|
260
|
+
| `stages` | `list[ContextDriftStage]` | Per-stage snapshots (`model_hash`, `tools_version`, `strategy_snapshot`, `operator_scope`) |
|
|
261
|
+
| `raw` | `dict` | Unmodified API response |
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Timing Breakdown
|
|
266
|
+
|
|
267
|
+
Anchor the full decision chronology on-chain so forensic auditors can distinguish real-time reasoning from post-hoc reconstruction. Pass a `TimingBreakdown` dict to `certify_with_confidence()`:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from xproof import XProofClient, TimingBreakdown
|
|
271
|
+
import time
|
|
272
|
+
|
|
273
|
+
client = XProofClient(api_key="pm_your_key")
|
|
274
|
+
|
|
275
|
+
# Capture timestamps at each lifecycle event
|
|
276
|
+
instruction_ts = "2026-04-20T14:30:00Z" # when the agent received the task
|
|
277
|
+
reasoning_ts = "2026-04-20T14:30:01Z" # when reasoning/planning started
|
|
278
|
+
action_ts = "2026-04-20T14:30:05Z" # when the action was executed
|
|
279
|
+
|
|
280
|
+
timing: TimingBreakdown = {
|
|
281
|
+
"instruction_received_at": instruction_ts,
|
|
282
|
+
"reasoning_started_at": reasoning_ts,
|
|
283
|
+
"action_taken_at": action_ts,
|
|
284
|
+
"jurisdiction_type": "autonomous_inference", # legal accountability class
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
cert = client.certify_with_confidence(
|
|
288
|
+
file_hash="e3b0c44298fc1c149afb...",
|
|
289
|
+
file_name="trade-decision.json",
|
|
290
|
+
author="trading-agent",
|
|
291
|
+
confidence_level=0.97,
|
|
292
|
+
threshold_stage="pre-commitment",
|
|
293
|
+
decision_id="trade-xyz-2026",
|
|
294
|
+
reversibility_class="irreversible",
|
|
295
|
+
timing=timing,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# The server echoes timing_breakdown with computed durations
|
|
299
|
+
tb = cert.timing_breakdown
|
|
300
|
+
if tb:
|
|
301
|
+
print(f"Thinking time : {tb.get('reasoning_duration_ms')} ms")
|
|
302
|
+
print(f"Total latency : {tb.get('total_duration_ms')} ms")
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### jurisdiction_type
|
|
306
|
+
|
|
307
|
+
The `jurisdiction_type` field records the legal accountability classification for the decision:
|
|
308
|
+
|
|
309
|
+
| Value | Meaning |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `instruction_following` | Agent executed an explicit human instruction — accountability follows the principal |
|
|
312
|
+
| `autonomous_inference` | Agent reached its own conclusion — agent and its operator bear primary accountability |
|
|
313
|
+
| `human_approved` | Agent recommended; a human explicitly approved — shared accountability |
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from xproof import JURISDICTION_TYPES
|
|
317
|
+
|
|
318
|
+
print(JURISDICTION_TYPES)
|
|
319
|
+
# ('instruction_following', 'autonomous_inference', 'human_approved')
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Reading timing_breakdown from a Certification
|
|
323
|
+
|
|
324
|
+
When the server echoes the timestamps it also computes:
|
|
325
|
+
- `reasoning_duration_ms` — milliseconds between `reasoning_started_at` and `action_taken_at`
|
|
326
|
+
- `total_duration_ms` — milliseconds between `instruction_received_at` and `action_taken_at`
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
tb = cert.timing_breakdown # TimingBreakdown | None
|
|
330
|
+
if tb is not None:
|
|
331
|
+
print(tb["instruction_received_at"]) # "2026-04-20T14:30:00Z"
|
|
332
|
+
print(tb["reasoning_duration_ms"]) # 4000
|
|
333
|
+
print(tb["total_duration_ms"]) # 5000
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
228
338
|
## Governance & Policy Enforcement
|
|
229
339
|
|
|
230
340
|
xProof detects automatically when an agent acted with insufficient confidence on an irreversible action — and writes the evidence on-chain before you ever open an incident report.
|
|
@@ -265,6 +375,8 @@ if not check.policy_compliant:
|
|
|
265
375
|
# → confidence 0.72 is below the required threshold of 0.95
|
|
266
376
|
```
|
|
267
377
|
|
|
378
|
+
Prefer `get_policy_check()` when you only need a fast governance gate. It avoids fetching the full confidence trail, which is better reserved for post-incident review or detailed forensic analysis.
|
|
379
|
+
|
|
268
380
|
### Full confidence trail with policy result
|
|
269
381
|
|
|
270
382
|
```python
|
|
@@ -296,11 +408,9 @@ from xproof.exceptions import PolicyViolationError
|
|
|
296
408
|
certify = XProofCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
297
409
|
|
|
298
410
|
decision = {
|
|
299
|
-
"action": "
|
|
300
|
-
"scope": "
|
|
301
|
-
"
|
|
302
|
-
"retention_policy_checked": True,
|
|
303
|
-
"legal_hold_clear": True,
|
|
411
|
+
"action": "delete_pii_records",
|
|
412
|
+
"scope": "eu-region",
|
|
413
|
+
"count": 15_000,
|
|
304
414
|
}
|
|
305
415
|
decision_id = "del-run-2026-04-20"
|
|
306
416
|
|
|
@@ -311,10 +421,10 @@ try:
|
|
|
311
421
|
"threshold_stage": "pre-commitment",
|
|
312
422
|
"decision_id": decision_id,
|
|
313
423
|
"reversibility_class": "irreversible",
|
|
314
|
-
"why": "Scheduled GDPR
|
|
424
|
+
"why": "Scheduled GDPR retention cleanup",
|
|
315
425
|
})
|
|
316
426
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
317
|
-
#
|
|
427
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
318
428
|
except PolicyViolationError as exc:
|
|
319
429
|
for v in exc.violations:
|
|
320
430
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -346,7 +456,7 @@ tx_hash = await certify.arun({
|
|
|
346
456
|
"threshold_stage": "pre-commitment",
|
|
347
457
|
"decision_id": decision_id,
|
|
348
458
|
"reversibility_class": "irreversible",
|
|
349
|
-
"why": "Scheduled GDPR
|
|
459
|
+
"why": "Scheduled GDPR retention cleanup",
|
|
350
460
|
})
|
|
351
461
|
```
|
|
352
462
|
|
|
@@ -379,11 +489,9 @@ from xproof.exceptions import PolicyViolationError
|
|
|
379
489
|
certify = XProofCrewCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
380
490
|
|
|
381
491
|
decision = {
|
|
382
|
-
"action": "
|
|
383
|
-
"scope": "
|
|
384
|
-
"
|
|
385
|
-
"retention_policy_checked": True,
|
|
386
|
-
"legal_hold_clear": True,
|
|
492
|
+
"action": "delete_pii_records",
|
|
493
|
+
"scope": "eu-region",
|
|
494
|
+
"count": 15_000,
|
|
387
495
|
}
|
|
388
496
|
decision_id = "del-run-2026-04-20"
|
|
389
497
|
|
|
@@ -394,10 +502,10 @@ try:
|
|
|
394
502
|
threshold_stage="pre-commitment",
|
|
395
503
|
decision_id=decision_id,
|
|
396
504
|
reversibility_class="irreversible",
|
|
397
|
-
why="Scheduled GDPR
|
|
505
|
+
why="Scheduled GDPR retention cleanup",
|
|
398
506
|
)
|
|
399
507
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
400
|
-
#
|
|
508
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
401
509
|
except PolicyViolationError as exc:
|
|
402
510
|
for v in exc.violations:
|
|
403
511
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -428,11 +536,9 @@ from xproof.integrations.autogen import xproof_certify_decision
|
|
|
428
536
|
from xproof.exceptions import PolicyViolationError
|
|
429
537
|
|
|
430
538
|
decision = {
|
|
431
|
-
"action": "
|
|
432
|
-
"scope": "
|
|
433
|
-
"
|
|
434
|
-
"retention_policy_checked": True,
|
|
435
|
-
"legal_hold_clear": True,
|
|
539
|
+
"action": "delete_pii_records",
|
|
540
|
+
"scope": "eu-region",
|
|
541
|
+
"count": 15_000,
|
|
436
542
|
}
|
|
437
543
|
decision_id = "del-run-2026-04-20"
|
|
438
544
|
|
|
@@ -443,12 +549,12 @@ try:
|
|
|
443
549
|
threshold_stage="pre-commitment",
|
|
444
550
|
decision_id=decision_id,
|
|
445
551
|
reversibility_class="irreversible",
|
|
446
|
-
why="Scheduled GDPR
|
|
552
|
+
why="Scheduled GDPR retention cleanup",
|
|
447
553
|
author="data-hygiene-agent",
|
|
448
554
|
api_key="pm_...",
|
|
449
555
|
)
|
|
450
556
|
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
451
|
-
#
|
|
557
|
+
# delete_pii_records(decision["scope"]) # your execution here
|
|
452
558
|
except PolicyViolationError as exc:
|
|
453
559
|
for v in exc.violations:
|
|
454
560
|
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
@@ -489,13 +595,9 @@ def hash_string(s: str) -> str:
|
|
|
489
595
|
# chain-of-thought or tool-call output produced just before execution.)
|
|
490
596
|
|
|
491
597
|
decision = {
|
|
492
|
-
"action": "
|
|
493
|
-
"scope": "
|
|
494
|
-
"
|
|
495
|
-
"retention_policy_checked": True,
|
|
496
|
-
"legal_hold_clear": True,
|
|
497
|
-
"agent": "data-hygiene-agent",
|
|
498
|
-
"run_id": "del-run-2026-04-20",
|
|
598
|
+
"action": "delete_pii_records",
|
|
599
|
+
"scope": "eu-region",
|
|
600
|
+
"count": 15_000,
|
|
499
601
|
}
|
|
500
602
|
decision_id = "del-run-2026-04-20"
|
|
501
603
|
reasoning_hash = hash_string(json.dumps(decision, sort_keys=True))
|
|
@@ -531,7 +633,7 @@ if not check.policy_compliant:
|
|
|
531
633
|
|
|
532
634
|
# ── Step 4: Execute only when compliant ──────────────────────────────────────
|
|
533
635
|
print(f"Policy compliant — proceeding with deletion (cert: {cert.transaction_hash})")
|
|
534
|
-
#
|
|
636
|
+
# delete_pii_records(decision["scope"]) # your actual execution here
|
|
535
637
|
```
|
|
536
638
|
|
|
537
639
|
**What happens if the agent's confidence is too low?**
|
|
@@ -640,6 +742,56 @@ shippers (Fluentd, the Datadog Agent, the CloudWatch agent) forward verbatim.
|
|
|
640
742
|
Create a log-based metric or alert on `event = "policy_violation"` to get
|
|
641
743
|
dashboard counts and threshold alerts with no extra instrumentation.
|
|
642
744
|
|
|
745
|
+
> **Runnable example** — `python-sdk/examples/compliance_observability.py` runs the full pattern with a mock client and verifies structured output. No API key needed.
|
|
746
|
+
|
|
747
|
+
#### Drop-in: CrewAI one-liner
|
|
748
|
+
|
|
749
|
+
`XProofCrewCertifyTool` wraps `certify_with_confidence` + `get_policy_check` into a single `run()` call. Replace the manual four-step loop with:
|
|
750
|
+
|
|
751
|
+
```python
|
|
752
|
+
from xproof.integrations.crewai import XProofCrewCertifyTool
|
|
753
|
+
from xproof.exceptions import PolicyViolationError
|
|
754
|
+
|
|
755
|
+
certify = XProofCrewCertifyTool(api_key="pm_...", author="compliance-agent")
|
|
756
|
+
|
|
757
|
+
try:
|
|
758
|
+
result = certify.run(
|
|
759
|
+
decision_text=json.dumps(decision),
|
|
760
|
+
confidence_level=0.91,
|
|
761
|
+
reversibility_class="irreversible",
|
|
762
|
+
decision_id="trade-xyz-2026",
|
|
763
|
+
)
|
|
764
|
+
except PolicyViolationError as exc:
|
|
765
|
+
for v in exc.violations:
|
|
766
|
+
_emit_violation("trade-xyz-2026", v) # reuse _emit_violation from above
|
|
767
|
+
raise RuntimeError("Action aborted: policy compliance check failed.") from exc
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
#### Drop-in: AutoGen one-liner
|
|
771
|
+
|
|
772
|
+
`xproof_certify_decision` is a plain callable — register it as a function tool on any `ConversableAgent` or call it directly:
|
|
773
|
+
|
|
774
|
+
```python
|
|
775
|
+
from xproof.integrations.autogen import xproof_certify_decision
|
|
776
|
+
from xproof.exceptions import PolicyViolationError
|
|
777
|
+
|
|
778
|
+
try:
|
|
779
|
+
result = xproof_certify_decision(
|
|
780
|
+
api_key="pm_...",
|
|
781
|
+
decision_text=json.dumps(decision),
|
|
782
|
+
confidence_level=0.91,
|
|
783
|
+
reversibility_class="irreversible",
|
|
784
|
+
decision_id="trade-xyz-2026",
|
|
785
|
+
author="autogen-agent",
|
|
786
|
+
)
|
|
787
|
+
except PolicyViolationError as exc:
|
|
788
|
+
for v in exc.violations:
|
|
789
|
+
_emit_violation("trade-xyz-2026", v)
|
|
790
|
+
raise RuntimeError("Action aborted: policy compliance check failed.") from exc
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Both tools write the violation evidence on-chain before raising — so the structured log and the chain anchor are always in sync.
|
|
794
|
+
|
|
643
795
|
### Three classes, one parameter
|
|
644
796
|
|
|
645
797
|
| `reversibility_class` | What it means | Policy threshold |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xproof"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -44,7 +44,7 @@ dev = [
|
|
|
44
44
|
"pre-commit>=3.0",
|
|
45
45
|
]
|
|
46
46
|
llamaindex = [
|
|
47
|
-
"llama-index-core>=0.
|
|
47
|
+
"llama-index-core>=0.14.20",
|
|
48
48
|
]
|
|
49
49
|
autogen = [
|
|
50
50
|
"pyautogen>=0.2.0",
|
|
@@ -71,6 +71,7 @@ include = ["xproof*"]
|
|
|
71
71
|
xproof = ["py.typed"]
|
|
72
72
|
|
|
73
73
|
[tool.pytest.ini_options]
|
|
74
|
+
asyncio_mode = "auto"
|
|
74
75
|
markers = [
|
|
75
76
|
"integration: marks tests that hit the live xProof API (deselect with '-m \"not integration\"')",
|
|
76
77
|
]
|