proofledger 0.3.0__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proofledger
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Framework-agnostic, tamper-evident audit layer for AI agents. Hash chains verify byte-for-byte against the ProofLedger TypeScript SDK and dashboard.
5
5
  Project-URL: Homepage, https://proofledger.dev
6
6
  Project-URL: Repository, https://github.com/jorama/proofledger
@@ -18,7 +18,13 @@ from __future__ import annotations
18
18
 
19
19
  from typing import Any, Callable, Dict, Optional, TypeVar
20
20
 
21
- from .client import RunHandle, ProofLedgerClient, PolicyBlockedError, iso_now
21
+ from .client import (
22
+ RunHandle,
23
+ ProofLedgerClient,
24
+ PolicyBlockedError,
25
+ ApprovalDeniedError,
26
+ iso_now,
27
+ )
22
28
  from .hashing import (
23
29
  GENESIS_HASH,
24
30
  ChainVerificationResult,
@@ -63,6 +69,7 @@ __all__ = [
63
69
  "get_trust_score",
64
70
  "flush",
65
71
  "PolicyBlockedError",
72
+ "ApprovalDeniedError",
66
73
  # Adapters
67
74
  "instrument",
68
75
  "wrap_openai",
@@ -127,6 +134,8 @@ def enable(
127
134
  version: Optional[str] = None,
128
135
  auto_capture: bool = True,
129
136
  policy_mode: str = "monitor",
137
+ approval_interval_ms: float = 2000,
138
+ approval_timeout_ms: float = 300_000,
130
139
  ) -> ProofLedgerClient:
131
140
  """Initialize the global client and return it for advanced use."""
132
141
  global _global_client
@@ -146,6 +155,8 @@ def enable(
146
155
  version=version,
147
156
  auto_capture=auto_capture,
148
157
  policy_mode=policy_mode,
158
+ approval_interval_ms=approval_interval_ms,
159
+ approval_timeout_ms=approval_timeout_ms,
149
160
  )
150
161
  return _global_client
151
162
 
@@ -22,7 +22,13 @@ from .hashing import (
22
22
  from .pricing import estimate_cost
23
23
  from .transport import HttpTransport, LocalTransport, Transport
24
24
 
25
- __all__ = ["ProofLedgerClient", "RunHandle", "PolicyBlockedError", "iso_now"]
25
+ __all__ = [
26
+ "ProofLedgerClient",
27
+ "RunHandle",
28
+ "PolicyBlockedError",
29
+ "ApprovalDeniedError",
30
+ "iso_now",
31
+ ]
26
32
 
27
33
  T = TypeVar("T")
28
34
 
@@ -97,6 +103,8 @@ class ProofLedgerClient:
97
103
  version: Optional[str] = None,
98
104
  auto_capture: bool = True,
99
105
  policy_mode: str = "monitor",
106
+ approval_interval_ms: float = 2000,
107
+ approval_timeout_ms: float = 300_000,
100
108
  ) -> None:
101
109
  use_local = local is True or not api_key
102
110
  self.config: Dict[str, Any] = {
@@ -114,6 +122,8 @@ class ProofLedgerClient:
114
122
  "version": version,
115
123
  "auto_capture": auto_capture is not False,
116
124
  "policy_mode": policy_mode if policy_mode in ("monitor", "enforce") else "monitor",
125
+ "approval_interval_ms": approval_interval_ms,
126
+ "approval_timeout_ms": approval_timeout_ms,
117
127
  }
118
128
 
119
129
  #: Active agent identity for trust events. The private key lives in
@@ -696,8 +706,21 @@ class ProofLedgerClient:
696
706
  }
697
707
  result = self.transport.send_trust_event(payload)
698
708
  policy = result.get("policy") or {}
699
- if self.config["policy_mode"] == "enforce" and policy.get("decision") == "block":
700
- raise PolicyBlockedError(policy)
709
+ if self.config["policy_mode"] == "enforce":
710
+ if policy.get("decision") == "block":
711
+ raise PolicyBlockedError(policy)
712
+ # require_approval GATES the action: block until a human decides.
713
+ # Fail closed — denied or timed-out-pending both raise.
714
+ approval = result.get("approval") or {}
715
+ if policy.get("decision") == "require_approval" and approval.get("id"):
716
+ status = self.await_approval(
717
+ approval["id"],
718
+ interval_ms=self.config["approval_interval_ms"],
719
+ timeout_ms=self.config["approval_timeout_ms"],
720
+ )
721
+ if status != "approved":
722
+ raise ApprovalDeniedError(approval["id"], status, action)
723
+ approval["status"] = "approved"
701
724
  return result
702
725
 
703
726
  def log_decision(self, **kwargs: Any) -> Dict[str, Any]:
@@ -775,6 +798,21 @@ class PolicyBlockedError(RuntimeError):
775
798
  self.evaluation = evaluation
776
799
 
777
800
 
801
+ class ApprovalDeniedError(RuntimeError):
802
+ """Raised in enforce mode when a require_approval action is denied — or
803
+ still pending when the wait times out (fail-closed)."""
804
+
805
+ def __init__(self, approval_id: str, status: str, title: Optional[str] = None) -> None:
806
+ suffix = f": {title}" if title else ""
807
+ super().__init__(
808
+ f"ProofLedger approval timed out while pending{suffix}"
809
+ if status == "pending"
810
+ else f"ProofLedger approval denied{suffix}"
811
+ )
812
+ self.approval_id = approval_id
813
+ self.status = status
814
+
815
+
778
816
  def _now_ms() -> float:
779
817
  """Epoch milliseconds, matching JS ``Date.now()``."""
780
818
  return time.time() * 1000.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "proofledger"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Framework-agnostic, tamper-evident audit layer for AI agents. Hash chains verify byte-for-byte against the ProofLedger TypeScript SDK and dashboard."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes