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.
Files changed (37) hide show
  1. {xproof-0.2.7 → xproof-0.2.8}/LICENSE +1 -1
  2. {xproof-0.2.7 → xproof-0.2.8}/PKG-INFO +184 -32
  3. {xproof-0.2.7 → xproof-0.2.8}/README.md +182 -30
  4. {xproof-0.2.7 → xproof-0.2.8}/pyproject.toml +3 -2
  5. xproof-0.2.8/tests/test_client.py +1377 -0
  6. xproof-0.2.8/tests/test_compliance_observability_example.py +54 -0
  7. {xproof-0.2.7 → xproof-0.2.8}/tests/test_confidence_trail.py +21 -10
  8. {xproof-0.2.7 → xproof-0.2.8}/tests/test_context_drift.py +1 -0
  9. xproof-0.2.8/tests/test_integration_policy_check.py +104 -0
  10. xproof-0.2.8/tests/test_langchain_callback_demo.py +210 -0
  11. xproof-0.2.8/tests/test_langchain_certify_tool_demo.py +113 -0
  12. {xproof-0.2.7 → xproof-0.2.8}/tests/test_langchain_tool.py +38 -15
  13. {xproof-0.2.7 → xproof-0.2.8}/tests/test_policy_check.py +33 -22
  14. xproof-0.2.8/tests/test_version.py +45 -0
  15. {xproof-0.2.7 → xproof-0.2.8}/xproof/__init__.py +13 -2
  16. {xproof-0.2.7 → xproof-0.2.8}/xproof/client.py +111 -30
  17. {xproof-0.2.7 → xproof-0.2.8}/xproof/exceptions.py +15 -3
  18. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/__init__.py +11 -9
  19. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/autogen.py +5 -5
  20. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/crewai.py +27 -21
  21. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/deerflow.py +8 -6
  22. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/fetchai.py +67 -16
  23. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/langchain.py +24 -18
  24. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/llamaindex.py +30 -23
  25. {xproof-0.2.7 → xproof-0.2.8}/xproof/integrations/openai_agents.py +35 -37
  26. {xproof-0.2.7 → xproof-0.2.8}/xproof/langchain_tool.py +10 -16
  27. {xproof-0.2.7 → xproof-0.2.8}/xproof/models.py +145 -48
  28. {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/PKG-INFO +184 -32
  29. {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/SOURCES.txt +5 -0
  30. {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/requires.txt +1 -1
  31. xproof-0.2.7/tests/test_client.py +0 -637
  32. {xproof-0.2.7 → xproof-0.2.8}/setup.cfg +0 -0
  33. {xproof-0.2.7 → xproof-0.2.8}/tests/test_integration.py +0 -0
  34. {xproof-0.2.7 → xproof-0.2.8}/xproof/py.typed +0 -0
  35. {xproof-0.2.7 → xproof-0.2.8}/xproof/utils.py +0 -0
  36. {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/dependency_links.txt +0 -0
  37. {xproof-0.2.7 → xproof-0.2.8}/xproof.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 xProof
3
+ Copyright (c) 2026 xProof
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xproof
3
- Version: 0.2.7
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.13.0; extra == "llamaindex"
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": "delete_customer_records",
346
- "scope": "inactive_accounts",
347
- "records_affected": 4821,
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 data-retention cleanup",
470
+ "why": "Scheduled GDPR retention cleanup",
361
471
  })
362
472
  print(f"Policy compliant — proceeding (tx: {tx_hash})")
363
- # delete_customer_records(decision["scope"]) # your execution here
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 data-retention cleanup",
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": "delete_customer_records",
429
- "scope": "inactive_accounts",
430
- "records_affected": 4821,
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 data-retention cleanup",
551
+ why="Scheduled GDPR retention cleanup",
444
552
  )
445
553
  print(f"Policy compliant — proceeding (tx: {tx_hash})")
446
- # delete_customer_records(decision["scope"]) # your execution here
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": "delete_customer_records",
478
- "scope": "inactive_accounts",
479
- "records_affected": 4821,
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 data-retention cleanup",
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
- # delete_customer_records(decision["scope"]) # your execution here
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": "delete_customer_records",
539
- "scope": "inactive_accounts",
540
- "records_affected": 4821,
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
- # delete_customer_records(decision["scope"]) # your actual execution here
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": "delete_customer_records",
300
- "scope": "inactive_accounts",
301
- "records_affected": 4821,
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 data-retention cleanup",
424
+ "why": "Scheduled GDPR retention cleanup",
315
425
  })
316
426
  print(f"Policy compliant — proceeding (tx: {tx_hash})")
317
- # delete_customer_records(decision["scope"]) # your execution here
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 data-retention cleanup",
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": "delete_customer_records",
383
- "scope": "inactive_accounts",
384
- "records_affected": 4821,
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 data-retention cleanup",
505
+ why="Scheduled GDPR retention cleanup",
398
506
  )
399
507
  print(f"Policy compliant — proceeding (tx: {tx_hash})")
400
- # delete_customer_records(decision["scope"]) # your execution here
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": "delete_customer_records",
432
- "scope": "inactive_accounts",
433
- "records_affected": 4821,
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 data-retention cleanup",
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
- # delete_customer_records(decision["scope"]) # your execution here
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": "delete_customer_records",
493
- "scope": "inactive_accounts",
494
- "records_affected": 4821,
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
- # delete_customer_records(decision["scope"]) # your actual execution here
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"
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.13.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
  ]