proofledger 0.2.0__tar.gz → 0.3.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.2.0
3
+ Version: 0.3.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,7 @@ from __future__ import annotations
18
18
 
19
19
  from typing import Any, Callable, Dict, Optional, TypeVar
20
20
 
21
- from .client import RunHandle, ProofLedgerClient, iso_now
21
+ from .client import RunHandle, ProofLedgerClient, PolicyBlockedError, iso_now
22
22
  from .hashing import (
23
23
  GENESIS_HASH,
24
24
  ChainVerificationResult,
@@ -49,6 +49,20 @@ __all__ = [
49
49
  "require_approval",
50
50
  "register_agent_identity",
51
51
  "send_agent_message",
52
+ # Phase 1 AI Trust Platform
53
+ "register_agent",
54
+ "identify_agent",
55
+ "log_decision",
56
+ "log_tool_call",
57
+ "log_api_call",
58
+ "log_workflow_step",
59
+ "log_trust_event",
60
+ "evaluate_policy",
61
+ "sign_event",
62
+ "verify_event",
63
+ "get_trust_score",
64
+ "flush",
65
+ "PolicyBlockedError",
52
66
  # Adapters
53
67
  "instrument",
54
68
  "wrap_openai",
@@ -106,6 +120,13 @@ def enable(
106
120
  local_dir: Optional[str] = None,
107
121
  silent: bool = False,
108
122
  disabled: bool = False,
123
+ agent_name: Optional[str] = None,
124
+ owner: Optional[str] = None,
125
+ framework: Optional[str] = None,
126
+ model: Optional[str] = None,
127
+ version: Optional[str] = None,
128
+ auto_capture: bool = True,
129
+ policy_mode: str = "monitor",
109
130
  ) -> ProofLedgerClient:
110
131
  """Initialize the global client and return it for advanced use."""
111
132
  global _global_client
@@ -118,6 +139,13 @@ def enable(
118
139
  local_dir=local_dir,
119
140
  silent=silent,
120
141
  disabled=disabled,
142
+ agent_name=agent_name,
143
+ owner=owner,
144
+ framework=framework,
145
+ model=model,
146
+ version=version,
147
+ auto_capture=auto_capture,
148
+ policy_mode=policy_mode,
121
149
  )
122
150
  return _global_client
123
151
 
@@ -185,13 +213,70 @@ def send_agent_message(**kwargs: Any) -> Dict[str, Any]:
185
213
  return _get_client().send_agent_message(**kwargs)
186
214
 
187
215
 
216
+ # --- Phase 1: AI Trust Platform ----------------------------------------------
217
+
218
+
219
+ def register_agent(**kwargs: Any) -> Dict[str, Any]:
220
+ """Register this agent's identity. See ProofLedgerClient.register_agent."""
221
+ return _get_client().register_agent(**kwargs)
222
+
223
+
224
+ def identify_agent(agent_id: str, private_key: Optional[str] = None) -> None:
225
+ """Set the active agent identity (and optional signing key)."""
226
+ return _get_client().identify_agent(agent_id, private_key)
227
+
228
+
229
+ def log_trust_event(**kwargs: Any) -> Dict[str, Any]:
230
+ """Log one event through the trust pipeline."""
231
+ return _get_client().log_trust_event(**kwargs)
232
+
233
+
234
+ def log_decision(**kwargs: Any) -> Dict[str, Any]:
235
+ """Log an agent decision."""
236
+ return _get_client().log_decision(**kwargs)
237
+
238
+
239
+ def log_tool_call(tool_name: str, **kwargs: Any) -> Dict[str, Any]:
240
+ """Log a tool/MCP call."""
241
+ return _get_client().log_tool_call(tool_name, **kwargs)
242
+
243
+
244
+ def log_api_call(api_endpoint: str, **kwargs: Any) -> Dict[str, Any]:
245
+ """Log an outbound API call."""
246
+ return _get_client().log_api_call(api_endpoint, **kwargs)
247
+
248
+
249
+ def log_workflow_step(
250
+ workflow_id: str, status: Optional[str] = None, **kwargs: Any
251
+ ) -> Dict[str, Any]:
252
+ """Log a workflow step (status="completed"/"failed" closes the workflow)."""
253
+ return _get_client().log_workflow_step(workflow_id, status, **kwargs)
254
+
255
+
256
+ def evaluate_policy(**kwargs: Any) -> Dict[str, Any]:
257
+ """Dry-run the policy engine without logging or changing trust."""
258
+ return _get_client().evaluate_policy(**kwargs)
259
+
260
+
261
+ def get_trust_score(agent_id: Optional[str] = None) -> Dict[str, Any]:
262
+ """Fetch the agent's live trust score and level."""
263
+ return _get_client().get_trust_score(agent_id)
264
+
265
+
266
+ def flush() -> None:
267
+ """Await pending sends (no-op in the sync Python SDK; API parity)."""
268
+ return _get_client().flush()
269
+
270
+
188
271
  # Adapters (imported lazily-safe; they resolve the global client at call time).
189
272
  from .adapters import instrument, wrap_openai, create_langchain_handler # noqa: E402
190
273
 
191
- # Signing (Phase 6) — needs the optional `cryptography` extra.
274
+ # Signing — needs the optional `cryptography` extra.
192
275
  from .signing import ( # noqa: E402
193
276
  create_agent_identity,
194
277
  sign_message,
195
278
  verify_message,
279
+ sign_event,
280
+ verify_event,
196
281
  signing_available,
197
282
  )
@@ -22,7 +22,7 @@ from .hashing import (
22
22
  from .pricing import estimate_cost
23
23
  from .transport import HttpTransport, LocalTransport, Transport
24
24
 
25
- __all__ = ["ProofLedgerClient", "RunHandle", "iso_now"]
25
+ __all__ = ["ProofLedgerClient", "RunHandle", "PolicyBlockedError", "iso_now"]
26
26
 
27
27
  T = TypeVar("T")
28
28
 
@@ -90,6 +90,13 @@ class ProofLedgerClient:
90
90
  local_dir: Optional[str] = None,
91
91
  silent: bool = False,
92
92
  disabled: bool = False,
93
+ agent_name: Optional[str] = None,
94
+ owner: Optional[str] = None,
95
+ framework: Optional[str] = None,
96
+ model: Optional[str] = None,
97
+ version: Optional[str] = None,
98
+ auto_capture: bool = True,
99
+ policy_mode: str = "monitor",
93
100
  ) -> None:
94
101
  use_local = local is True or not api_key
95
102
  self.config: Dict[str, Any] = {
@@ -99,8 +106,21 @@ class ProofLedgerClient:
99
106
  "base_url": base_url,
100
107
  "disabled": disabled is True,
101
108
  "silent": silent is True,
109
+ # Phase 1 trust platform defaults.
110
+ "agent_name": agent_name,
111
+ "owner": owner,
112
+ "framework": framework,
113
+ "model": model,
114
+ "version": version,
115
+ "auto_capture": auto_capture is not False,
116
+ "policy_mode": policy_mode if policy_mode in ("monitor", "enforce") else "monitor",
102
117
  }
103
118
 
119
+ #: Active agent identity for trust events. The private key lives in
120
+ #: memory only — never sent to the backend, never logged.
121
+ self._current_agent_id: Optional[str] = agent_name
122
+ self._current_private_key: Optional[str] = None
123
+
104
124
  self.transport: Transport = (
105
125
  LocalTransport(local_dir or ".proofledger")
106
126
  if use_local
@@ -551,6 +571,209 @@ class ProofLedgerClient:
551
571
  }
552
572
  )
553
573
 
574
+ # --- Phase 1: AI Trust Platform ------------------------------------------
575
+
576
+ def _require_agent_id(self, agent_id: Optional[str] = None) -> str:
577
+ resolved = agent_id or self._current_agent_id or self.config.get("agent_name")
578
+ if not resolved:
579
+ raise ValueError(
580
+ "ProofLedger: no agent_id. Pass one, call identify_agent(), "
581
+ "or set agent_name in enable()."
582
+ )
583
+ return resolved
584
+
585
+ def register_agent(self, **kwargs: Any) -> Dict[str, Any]:
586
+ """Register (or update) this agent's identity in the registry.
587
+
588
+ Uses ``enable()`` config as defaults. With ``generate_keys=True`` (the
589
+ default when no public key is supplied) the server returns the private
590
+ key ONCE; the client keeps it in memory for event signing.
591
+ """
592
+ agent_id = self._require_agent_id(kwargs.get("agent_id"))
593
+ payload = {
594
+ "projectId": self.config["project_id"],
595
+ "agentId": agent_id,
596
+ "name": kwargs.get("name") or self.config.get("agent_name") or agent_id,
597
+ "description": kwargs.get("description"),
598
+ "owner": kwargs.get("owner") or self.config.get("owner"),
599
+ "framework": kwargs.get("framework") or self.config.get("framework"),
600
+ "model": kwargs.get("model") or self.config.get("model"),
601
+ "version": kwargs.get("version") or self.config.get("version"),
602
+ "environment": kwargs.get("environment") or self.config["environment"],
603
+ "publicKey": kwargs.get("public_key"),
604
+ "generateKeys": kwargs.get(
605
+ "generate_keys", kwargs.get("public_key") is None
606
+ ),
607
+ }
608
+ result = self.transport.register_agent(payload)
609
+ self._current_agent_id = agent_id
610
+ if result.get("privateKey"):
611
+ self._current_private_key = result["privateKey"]
612
+ return result
613
+
614
+ def identify_agent(self, agent_id: str, private_key: Optional[str] = None) -> None:
615
+ """Set the active agent identity (and optional signing key)."""
616
+ self._current_agent_id = agent_id
617
+ if private_key:
618
+ self._current_private_key = private_key
619
+
620
+ def sign_event(self, event: Dict[str, Any], private_key: Optional[str] = None) -> str:
621
+ """Sign an event body (eventId, agentId, eventType, action, timestamp)."""
622
+ from .signing import sign_event as _sign_event
623
+
624
+ key = private_key or self._current_private_key
625
+ if not key:
626
+ raise ValueError(
627
+ "ProofLedger: no private key. register_agent() with generate_keys "
628
+ "or identify_agent(agent_id, private_key)."
629
+ )
630
+ return _sign_event(key, event)
631
+
632
+ def verify_event(
633
+ self, public_key: str, event: Dict[str, Any], signature: str
634
+ ) -> bool:
635
+ """Verify an event signature against a public key (offline check)."""
636
+ from .signing import verify_event as _verify_event
637
+
638
+ return _verify_event(public_key, event, signature)
639
+
640
+ def log_trust_event(self, **kwargs: Any) -> Dict[str, Any]:
641
+ """Log one event through the trust pipeline (policy → security → trust
642
+ → hash chain). Signs automatically when a private key is held. In
643
+ enforce mode raises :class:`PolicyBlockedError` on a block decision.
644
+ """
645
+ from .signing import signing_available
646
+
647
+ agent_id = self._require_agent_id(kwargs.get("agent_id"))
648
+ action = kwargs.get("action")
649
+ if not action:
650
+ raise ValueError("ProofLedger: action is required")
651
+ event_id = kwargs.get("event_id") or ids.new_id("evt")
652
+ timestamp = iso_now()
653
+ event_type = kwargs.get("event_type") or kwargs.get("event_category") or "custom"
654
+ signature = None
655
+ if (
656
+ not kwargs.get("unsigned")
657
+ and self._current_private_key
658
+ and signing_available()
659
+ and agent_id == (self._current_agent_id or agent_id)
660
+ ):
661
+ signature = self.sign_event(
662
+ {
663
+ "eventId": event_id,
664
+ "agentId": agent_id,
665
+ "eventType": event_type,
666
+ "action": action,
667
+ "timestamp": timestamp,
668
+ }
669
+ )
670
+ if self.config["disabled"]:
671
+ return {
672
+ "event": {"eventId": event_id, "agentId": agent_id, "action": action},
673
+ "policy": {"decision": "allow", "matched": [], "reason": "SDK disabled"},
674
+ "trust": {"before": 100, "after": 100, "level": "trusted"},
675
+ }
676
+ payload = {
677
+ "projectId": self.config["project_id"],
678
+ "agentId": agent_id,
679
+ "eventId": event_id,
680
+ "eventType": event_type,
681
+ "eventCategory": kwargs.get("event_category"),
682
+ "action": action,
683
+ "inputSummary": kwargs.get("input_summary"),
684
+ "outputSummary": kwargs.get("output_summary"),
685
+ "toolName": kwargs.get("tool_name"),
686
+ "toolType": kwargs.get("tool_type"),
687
+ "mcpServer": kwargs.get("mcp_server"),
688
+ "apiEndpoint": kwargs.get("api_endpoint"),
689
+ "workflowId": kwargs.get("workflow_id"),
690
+ "workflowName": kwargs.get("workflow_name"),
691
+ "userId": kwargs.get("user_id"),
692
+ "sensitiveAction": kwargs.get("sensitive_action"),
693
+ "metadata": kwargs.get("metadata"),
694
+ "timestamp": timestamp,
695
+ "signature": signature,
696
+ }
697
+ result = self.transport.send_trust_event(payload)
698
+ policy = result.get("policy") or {}
699
+ if self.config["policy_mode"] == "enforce" and policy.get("decision") == "block":
700
+ raise PolicyBlockedError(policy)
701
+ return result
702
+
703
+ def log_decision(self, **kwargs: Any) -> Dict[str, Any]:
704
+ """Log an agent decision (reasoning step, plan, choice)."""
705
+ kwargs.update(event_type="decision", event_category="decision")
706
+ return self.log_trust_event(**kwargs)
707
+
708
+ def log_tool_call(self, tool_name: str, **kwargs: Any) -> Dict[str, Any]:
709
+ """Log a tool/MCP call (unknown tools auto-register as unapproved)."""
710
+ kwargs.setdefault("action", f"Tool call: {tool_name}")
711
+ kwargs.update(
712
+ tool_name=tool_name, event_type="tool_call", event_category="tool_call"
713
+ )
714
+ return self.log_trust_event(**kwargs)
715
+
716
+ def log_api_call(self, api_endpoint: str, **kwargs: Any) -> Dict[str, Any]:
717
+ """Log an outbound API call."""
718
+ kwargs.setdefault("action", f"API call: {api_endpoint}")
719
+ kwargs.update(
720
+ api_endpoint=api_endpoint, event_type="api_call", event_category="api_call"
721
+ )
722
+ return self.log_trust_event(**kwargs)
723
+
724
+ def log_workflow_step(
725
+ self, workflow_id: str, status: Optional[str] = None, **kwargs: Any
726
+ ) -> Dict[str, Any]:
727
+ """Log a workflow step; pass status="completed"/"failed" to close it."""
728
+ event_type = (
729
+ "workflow_completed"
730
+ if status == "completed"
731
+ else "workflow_failed"
732
+ if status == "failed"
733
+ else kwargs.pop("event_type", None) or "workflow_step"
734
+ )
735
+ kwargs.update(
736
+ workflow_id=workflow_id, event_type=event_type, event_category="workflow"
737
+ )
738
+ return self.log_trust_event(**kwargs)
739
+
740
+ def evaluate_policy(self, **kwargs: Any) -> Dict[str, Any]:
741
+ """Dry-run the policy engine WITHOUT logging or changing trust."""
742
+ if self.config["disabled"]:
743
+ return {"decision": "allow", "matched": [], "reason": "SDK disabled"}
744
+ return self.transport.evaluate_policy(
745
+ {
746
+ "projectId": self.config["project_id"],
747
+ "agentId": self._require_agent_id(kwargs.get("agent_id")),
748
+ "eventType": kwargs.get("event_type"),
749
+ "eventCategory": kwargs.get("event_category"),
750
+ "toolName": kwargs.get("tool_name"),
751
+ "mcpServer": kwargs.get("mcp_server"),
752
+ "apiEndpoint": kwargs.get("api_endpoint"),
753
+ "sensitiveAction": kwargs.get("sensitive_action"),
754
+ }
755
+ )
756
+
757
+ def get_trust_score(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
758
+ """Fetch the agent's live trust score and level."""
759
+ return self.transport.get_trust_score(
760
+ self.config["project_id"], self._require_agent_id(agent_id)
761
+ )
762
+
763
+ def flush(self) -> None:
764
+ """No-op for API parity — the Python SDK sends synchronously."""
765
+ return None
766
+
767
+
768
+ class PolicyBlockedError(RuntimeError):
769
+ """Raised by log_* methods in enforce mode when policy blocks the action."""
770
+
771
+ def __init__(self, evaluation: Dict[str, Any]) -> None:
772
+ super().__init__(
773
+ f"ProofLedger policy blocked this action: {evaluation.get('reason')}"
774
+ )
775
+ self.evaluation = evaluation
776
+
554
777
 
555
778
  def _now_ms() -> float:
556
779
  """Epoch milliseconds, matching JS ``Date.now()``."""
@@ -21,7 +21,14 @@ try: # optional dependency
21
21
  except Exception: # pragma: no cover - exercised only without the extra
22
22
  _HAS_CRYPTO = False
23
23
 
24
- __all__ = ["create_agent_identity", "sign_message", "verify_message", "signing_available"]
24
+ __all__ = [
25
+ "create_agent_identity",
26
+ "sign_message",
27
+ "verify_message",
28
+ "sign_event",
29
+ "verify_event",
30
+ "signing_available",
31
+ ]
25
32
 
26
33
 
27
34
  def signing_available() -> bool:
@@ -64,6 +71,46 @@ def sign_message(private_key_b64: str, envelope: Dict[str, Any]) -> str:
64
71
  return base64.b64encode(priv.sign(data)).decode("ascii")
65
72
 
66
73
 
74
+ def _signable_event_body(event: Dict[str, Any]) -> str:
75
+ """Canonical body signed for a Phase 1 trust event.
76
+
77
+ Must stay in lockstep with the backend's ``signableEventBody`` and the TS
78
+ SDK's ``signEvent`` — the server verifies against exactly this JSON.
79
+ """
80
+ return canonicalize(
81
+ {
82
+ "eventId": event["eventId"],
83
+ "agentId": event["agentId"],
84
+ "eventType": event["eventType"],
85
+ "action": event["action"],
86
+ "timestamp": event["timestamp"],
87
+ }
88
+ )
89
+
90
+
91
+ def sign_event(private_key_b64: str, event: Dict[str, Any]) -> str:
92
+ """Sign a trust-platform event body. Returns a base64 signature.
93
+
94
+ ``event`` must contain eventId, agentId, eventType, action, timestamp.
95
+ """
96
+ _require()
97
+ priv = serialization.load_der_private_key(base64.b64decode(private_key_b64), password=None)
98
+ return base64.b64encode(priv.sign(_signable_event_body(event).encode("utf-8"))).decode("ascii")
99
+
100
+
101
+ def verify_event(public_key_b64: str, event: Dict[str, Any], signature_b64: str) -> bool:
102
+ """Verify a trust-platform event signature against an agent's public key."""
103
+ if not _HAS_CRYPTO:
104
+ return False
105
+ try:
106
+ pub = serialization.load_der_public_key(base64.b64decode(public_key_b64))
107
+ data = _signable_event_body(event).encode("utf-8")
108
+ pub.verify(base64.b64decode(signature_b64), data) # type: ignore[union-attr]
109
+ return True
110
+ except Exception:
111
+ return False
112
+
113
+
67
114
  def verify_message(public_key_b64: str, envelope: Dict[str, Any], signature_b64: str) -> bool:
68
115
  """Verify a signature against an agent's public key."""
69
116
  if not _HAS_CRYPTO:
@@ -64,6 +64,20 @@ class Transport(abc.ABC):
64
64
  @abc.abstractmethod
65
65
  def send_agent_message(self, payload: Dict[str, Any]) -> Dict[str, Any]: ...
66
66
 
67
+ # --- Phase 1 trust platform (non-abstract for backward compatibility) ----
68
+
69
+ def register_agent(self, payload: Dict[str, Any]) -> Dict[str, Any]:
70
+ raise NotImplementedError
71
+
72
+ def send_trust_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
73
+ raise NotImplementedError
74
+
75
+ def evaluate_policy(self, payload: Dict[str, Any]) -> Dict[str, Any]:
76
+ raise NotImplementedError
77
+
78
+ def get_trust_score(self, project_id: str, agent_id: str) -> Dict[str, Any]:
79
+ raise NotImplementedError
80
+
67
81
 
68
82
  class HttpTransport(Transport):
69
83
  """HTTP transport using only ``urllib`` from the standard library."""
@@ -145,6 +159,28 @@ class HttpTransport(Transport):
145
159
  data = self._request("/api/proofledger/messages", "POST", _drop_none(payload))
146
160
  return (data or {}).get("message", {})
147
161
 
162
+ # --- Phase 1 trust platform ----------------------------------------------
163
+
164
+ def register_agent(self, payload: Dict[str, Any]) -> Dict[str, Any]:
165
+ return self._request("/api/agents", "POST", _drop_none(payload)) or {}
166
+
167
+ def send_trust_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
168
+ return self._request("/api/events", "POST", _drop_none(payload)) or {}
169
+
170
+ def evaluate_policy(self, payload: Dict[str, Any]) -> Dict[str, Any]:
171
+ data = self._request("/api/policies/evaluate", "POST", _drop_none(payload))
172
+ return (data or {}).get("evaluation", {})
173
+
174
+ def get_trust_score(self, project_id: str, agent_id: str) -> Dict[str, Any]:
175
+ quoted_agent = urllib.parse.quote(agent_id, safe="")
176
+ quoted_project = urllib.parse.quote(project_id, safe="")
177
+ return (
178
+ self._request(
179
+ f"/api/agents/{quoted_agent}/trust?projectId={quoted_project}", "GET"
180
+ )
181
+ or {}
182
+ )
183
+
148
184
 
149
185
  class LocalTransport(Transport):
150
186
  """Local-first transport: in-memory store + best-effort JSONL log.
@@ -208,6 +244,98 @@ class LocalTransport(Transport):
208
244
  verified = bool(public_key) and verify_message(public_key, envelope, payload["signature"])
209
245
  return {"id": new_id("msg"), "verified": verified}
210
246
 
247
+ # --- Phase 1 trust platform (local approximations) ------------------------
248
+ # Local mode has no policy store or registry: everything is allowed, trust
249
+ # stays at 100, and signatures verify against locally-registered keys.
250
+
251
+ def register_agent(self, payload: Dict[str, Any]) -> Dict[str, Any]:
252
+ if not hasattr(self, "_trust_agents"):
253
+ self._trust_agents: Dict[str, Dict[str, Any]] = {}
254
+ agent_id = payload["agentId"]
255
+ existing = self._trust_agents.get(agent_id)
256
+ public_key = payload.get("publicKey") or (existing or {}).get("publicKey")
257
+ private_key = None
258
+ if not public_key and payload.get("generateKeys"):
259
+ from .signing import create_agent_identity, signing_available
260
+
261
+ if signing_available():
262
+ pair = create_agent_identity()
263
+ public_key = pair["publicKey"]
264
+ private_key = pair["privateKey"]
265
+ agent = {
266
+ "agentId": agent_id,
267
+ "name": payload.get("name") or agent_id,
268
+ "owner": payload.get("owner"),
269
+ "framework": payload.get("framework"),
270
+ "model": payload.get("model"),
271
+ "version": payload.get("version"),
272
+ "environment": payload.get("environment") or "development",
273
+ "publicKey": public_key,
274
+ "status": "active",
275
+ "trustScore": 100,
276
+ "trustLevel": "trusted",
277
+ }
278
+ self._trust_agents[agent_id] = agent
279
+ if public_key:
280
+ self._identities[agent_id] = public_key
281
+ result: Dict[str, Any] = {"agent": agent, "created": existing is None}
282
+ if private_key:
283
+ result["privateKey"] = private_key
284
+ return result
285
+
286
+ def send_trust_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
287
+ from .signing import verify_event
288
+
289
+ agent_id = payload["agentId"]
290
+ signature_valid = None
291
+ if payload.get("signature"):
292
+ public_key = self._identities.get(agent_id)
293
+ signature_valid = bool(public_key) and verify_event(
294
+ public_key,
295
+ {
296
+ "eventId": payload.get("eventId"),
297
+ "agentId": agent_id,
298
+ "eventType": payload.get("eventType"),
299
+ "action": payload.get("action"),
300
+ "timestamp": payload.get("timestamp"),
301
+ },
302
+ payload["signature"],
303
+ )
304
+ return {
305
+ "event": {
306
+ "eventId": payload.get("eventId") or new_id("evt"),
307
+ "agentId": agent_id,
308
+ "eventType": payload.get("eventType"),
309
+ "eventCategory": payload.get("eventCategory") or "system",
310
+ "action": payload.get("action"),
311
+ "policyDecision": "allow",
312
+ "signatureValid": signature_valid,
313
+ "hash": "",
314
+ "previousHash": "",
315
+ },
316
+ "policy": {
317
+ "decision": "allow",
318
+ "matched": [],
319
+ "reason": "Local mode — policies are not evaluated",
320
+ },
321
+ "trust": {"before": 100, "after": 100, "level": "trusted"},
322
+ }
323
+
324
+ def evaluate_policy(self, payload: Dict[str, Any]) -> Dict[str, Any]:
325
+ return {
326
+ "decision": "allow",
327
+ "matched": [],
328
+ "reason": "Local mode — policies are not evaluated",
329
+ }
330
+
331
+ def get_trust_score(self, project_id: str, agent_id: str) -> Dict[str, Any]:
332
+ return {
333
+ "agentId": agent_id,
334
+ "trustScore": 100,
335
+ "trustLevel": "trusted",
336
+ "status": "active",
337
+ }
338
+
211
339
  def _persist(self, event: Dict[str, Any]) -> None:
212
340
  try:
213
341
  if not self._dir_ready:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "proofledger"
7
- version = "0.2.0"
7
+ version = "0.3.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