veritera 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.
- veritera-0.1.0/PKG-INFO +14 -0
- veritera-0.1.0/pyproject.toml +22 -0
- veritera-0.1.0/setup.cfg +4 -0
- veritera-0.1.0/veritera/__init__.py +6 -0
- veritera-0.1.0/veritera/client.py +286 -0
- veritera-0.1.0/veritera.egg-info/PKG-INFO +14 -0
- veritera-0.1.0/veritera.egg-info/SOURCES.txt +8 -0
- veritera-0.1.0/veritera.egg-info/dependency_links.txt +1 -0
- veritera-0.1.0/veritera.egg-info/requires.txt +2 -0
- veritera-0.1.0/veritera.egg-info/top_level.txt +1 -0
veritera-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veritera
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Veritera Forge SDK — Verify AI agent decisions with cryptographic attestation
|
|
5
|
+
Author-email: Veritera AI <engineering@veritera.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://veritera.ai
|
|
8
|
+
Project-URL: Documentation, https://veritera.ai/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/VeriteraAI/veritera-website
|
|
10
|
+
Keywords: veritera,forge,ai,verification,agent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.25.0
|
|
14
|
+
Requires-Dist: cryptography>=41.0.0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "veritera"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Veritera Forge SDK — Verify AI agent decisions with cryptographic attestation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
authors = [{name = "Veritera AI", email = "engineering@veritera.ai"}]
|
|
9
|
+
keywords = ["veritera", "forge", "ai", "verification", "agent"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.25.0",
|
|
12
|
+
"cryptography>=41.0.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://veritera.ai"
|
|
17
|
+
Documentation = "https://veritera.ai/docs"
|
|
18
|
+
Repository = "https://github.com/VeriteraAI/veritera-website"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["setuptools>=68.0"]
|
|
22
|
+
build-backend = "setuptools.build_meta"
|
veritera-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Veritera Forge SDK — Python Client
|
|
3
|
+
Enterprise-grade AI agent decision verification.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from veritera import Veritera
|
|
7
|
+
|
|
8
|
+
forge = Veritera(api_key="vt_live_...")
|
|
9
|
+
result = await forge.verify_decision(
|
|
10
|
+
agent_id="agent-1",
|
|
11
|
+
action="payment.create",
|
|
12
|
+
params={"amount": 100, "currency": "USD"},
|
|
13
|
+
policy="finance-controls",
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Optional
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
27
|
+
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
28
|
+
import base64
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ForgeError(Exception):
|
|
32
|
+
"""Structured error from the Forge API."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str, code: str = "unknown", status: int = 0, details: Any = None):
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.code = code
|
|
37
|
+
self.status = status
|
|
38
|
+
self.details = details
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RateLimitError(ForgeError):
|
|
42
|
+
"""Rate limit exceeded."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, message: str, retry_after_ms: int = 60000):
|
|
45
|
+
super().__init__(message, code="rate_limited", status=429)
|
|
46
|
+
self.retry_after_ms = retry_after_ms
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ConstraintResult:
|
|
51
|
+
type: str
|
|
52
|
+
result: str # "pass" | "fail" | "skip"
|
|
53
|
+
detail: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class VerifyResponse:
|
|
58
|
+
verified: bool
|
|
59
|
+
verdict: str
|
|
60
|
+
proof_id: str
|
|
61
|
+
agent_id: str
|
|
62
|
+
action: str
|
|
63
|
+
policy: Optional[str]
|
|
64
|
+
reason: Optional[str]
|
|
65
|
+
mode: str
|
|
66
|
+
proof_status: str
|
|
67
|
+
evaluated_constraints: list[ConstraintResult]
|
|
68
|
+
verification: dict[str, Any]
|
|
69
|
+
audit_id: str
|
|
70
|
+
latency_ms: float
|
|
71
|
+
timestamp: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class LocalVerifyResult:
|
|
76
|
+
valid: bool
|
|
77
|
+
key_version: Optional[int]
|
|
78
|
+
payload_hash: str
|
|
79
|
+
algorithm: str = "Ed25519"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Veritera:
|
|
83
|
+
"""Veritera Forge SDK Client."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
api_key: str,
|
|
88
|
+
base_url: str = "https://api.veritera.ai",
|
|
89
|
+
timeout: float = 10.0,
|
|
90
|
+
max_retries: int = 2,
|
|
91
|
+
debug: bool = False,
|
|
92
|
+
):
|
|
93
|
+
if not api_key:
|
|
94
|
+
raise ValueError("Forge SDK: api_key is required")
|
|
95
|
+
self._api_key = api_key
|
|
96
|
+
self._base_url = base_url.rstrip("/")
|
|
97
|
+
self._timeout = timeout
|
|
98
|
+
self._max_retries = max_retries
|
|
99
|
+
self._debug = debug
|
|
100
|
+
import os
|
|
101
|
+
_env = os.environ.get("PYTHON_ENV", os.environ.get("ENV", "production"))
|
|
102
|
+
self._client = httpx.AsyncClient(
|
|
103
|
+
base_url=self._base_url,
|
|
104
|
+
timeout=self._timeout,
|
|
105
|
+
headers={
|
|
106
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
"User-Agent": "veritera-python/0.1.0",
|
|
109
|
+
"X-Forge-SDK-Version": "0.1.0",
|
|
110
|
+
"X-Forge-Environment": _env,
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async def __aenter__(self):
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
async def __aexit__(self, *args):
|
|
118
|
+
await self.close()
|
|
119
|
+
|
|
120
|
+
async def close(self):
|
|
121
|
+
await self._client.aclose()
|
|
122
|
+
|
|
123
|
+
async def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict:
|
|
124
|
+
"""HTTP request with retry and backoff."""
|
|
125
|
+
last_error: Optional[Exception] = None
|
|
126
|
+
|
|
127
|
+
for attempt in range(self._max_retries + 1):
|
|
128
|
+
if attempt > 0:
|
|
129
|
+
delay = min(1.0 * (2 ** (attempt - 1)), 10.0)
|
|
130
|
+
if self._debug:
|
|
131
|
+
print(f"[Forge SDK] Retry {attempt}/{self._max_retries} after {delay:.1f}s")
|
|
132
|
+
await _async_sleep(delay)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
response = await self._client.request(
|
|
136
|
+
method, path,
|
|
137
|
+
json=body if body else None,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if response.status_code == 429:
|
|
141
|
+
retry_after = int(response.headers.get("Retry-After", "60")) * 1000
|
|
142
|
+
raise RateLimitError("Rate limit exceeded", retry_after)
|
|
143
|
+
|
|
144
|
+
if 400 <= response.status_code < 500:
|
|
145
|
+
data = response.json() if response.content else {}
|
|
146
|
+
raise ForgeError(
|
|
147
|
+
data.get("message", f"Request failed: {response.status_code}"),
|
|
148
|
+
code=data.get("error", "client_error"),
|
|
149
|
+
status=response.status_code,
|
|
150
|
+
details=data,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if response.status_code >= 500:
|
|
154
|
+
last_error = ForgeError(f"Server error: {response.status_code}", status=response.status_code)
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
return response.json()
|
|
158
|
+
|
|
159
|
+
except (httpx.TimeoutException, httpx.ConnectError) as e:
|
|
160
|
+
last_error = ForgeError(str(e), code="network_error", status=0)
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
raise last_error or ForgeError("Request failed after retries")
|
|
164
|
+
|
|
165
|
+
# ── Core: Verify a Decision ──
|
|
166
|
+
|
|
167
|
+
async def verify_decision(
|
|
168
|
+
self,
|
|
169
|
+
agent_id: str,
|
|
170
|
+
action: str,
|
|
171
|
+
params: Optional[dict[str, Any]] = None,
|
|
172
|
+
policy: Optional[str] = None,
|
|
173
|
+
allowed_actions: Optional[list[str]] = None,
|
|
174
|
+
delegation_id: Optional[str] = None,
|
|
175
|
+
require_zk_proof: bool = False,
|
|
176
|
+
) -> VerifyResponse:
|
|
177
|
+
"""Verify an AI agent decision against policy constraints."""
|
|
178
|
+
body: dict[str, Any] = {"agent_id": agent_id, "action": action}
|
|
179
|
+
if params:
|
|
180
|
+
body["params"] = params
|
|
181
|
+
if policy:
|
|
182
|
+
body["policy"] = policy
|
|
183
|
+
if allowed_actions:
|
|
184
|
+
body["allowed_actions"] = allowed_actions
|
|
185
|
+
if delegation_id:
|
|
186
|
+
body["delegation_id"] = delegation_id
|
|
187
|
+
if require_zk_proof:
|
|
188
|
+
body["require_zk_proof"] = True
|
|
189
|
+
|
|
190
|
+
raw = await self._request("POST", "/v1/verify", body)
|
|
191
|
+
return self._map_verify_response(raw)
|
|
192
|
+
|
|
193
|
+
# ── Proof Retrieval ──
|
|
194
|
+
|
|
195
|
+
async def get_proof(self, proof_id: str) -> dict:
|
|
196
|
+
"""Retrieve a verification proof by ID."""
|
|
197
|
+
return await self._request("GET", f"/v1/proofs/{proof_id}")
|
|
198
|
+
|
|
199
|
+
# ── Local Proof Verification ──
|
|
200
|
+
|
|
201
|
+
def verify_proof_locally(
|
|
202
|
+
self,
|
|
203
|
+
attestation: str,
|
|
204
|
+
attestation_payload: dict[str, Any],
|
|
205
|
+
public_key: str,
|
|
206
|
+
key_version: Optional[int] = None,
|
|
207
|
+
) -> LocalVerifyResult:
|
|
208
|
+
"""Verify a DPE attestation locally without any backend call."""
|
|
209
|
+
payload_bytes = json.dumps(attestation_payload).encode("utf-8")
|
|
210
|
+
payload_hash = hashlib.sha256(payload_bytes).hexdigest()
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
sig_bytes = base64.b64decode(attestation)
|
|
214
|
+
pub_key_der = base64.b64decode(public_key)
|
|
215
|
+
pub_key = load_der_public_key(pub_key_der)
|
|
216
|
+
|
|
217
|
+
if not isinstance(pub_key, Ed25519PublicKey):
|
|
218
|
+
return LocalVerifyResult(valid=False, key_version=key_version, payload_hash=payload_hash)
|
|
219
|
+
|
|
220
|
+
pub_key.verify(sig_bytes, payload_bytes)
|
|
221
|
+
return LocalVerifyResult(valid=True, key_version=key_version, payload_hash=payload_hash)
|
|
222
|
+
|
|
223
|
+
except Exception:
|
|
224
|
+
return LocalVerifyResult(valid=False, key_version=key_version, payload_hash=payload_hash)
|
|
225
|
+
|
|
226
|
+
# ── Delegation ──
|
|
227
|
+
|
|
228
|
+
async def create_delegation(
|
|
229
|
+
self,
|
|
230
|
+
agent_id: str,
|
|
231
|
+
allowed_actions: list[str],
|
|
232
|
+
constraints: Optional[dict] = None,
|
|
233
|
+
expires_in: Optional[str] = None,
|
|
234
|
+
) -> dict:
|
|
235
|
+
"""Create a scoped delegation for an agent."""
|
|
236
|
+
body: dict[str, Any] = {"agent_id": agent_id, "allowed_actions": allowed_actions}
|
|
237
|
+
if constraints:
|
|
238
|
+
body["constraints"] = constraints
|
|
239
|
+
if expires_in:
|
|
240
|
+
body["expires_in"] = expires_in
|
|
241
|
+
return await self._request("POST", "/v1/delegate", body)
|
|
242
|
+
|
|
243
|
+
# ── Usage ──
|
|
244
|
+
|
|
245
|
+
async def get_usage(self, period: Optional[str] = None) -> dict:
|
|
246
|
+
"""Get usage statistics for the current billing period."""
|
|
247
|
+
path = f"/v1/usage?period={period}" if period else "/v1/usage"
|
|
248
|
+
return await self._request("GET", path)
|
|
249
|
+
|
|
250
|
+
# ── Health ──
|
|
251
|
+
|
|
252
|
+
async def health(self) -> dict:
|
|
253
|
+
"""Check Forge API health."""
|
|
254
|
+
return await self._request("GET", "/v1/health")
|
|
255
|
+
|
|
256
|
+
# ── Response Mapping ──
|
|
257
|
+
|
|
258
|
+
def _map_verify_response(self, raw: dict) -> VerifyResponse:
|
|
259
|
+
verification = raw.get("verification", {})
|
|
260
|
+
constraints = raw.get("evaluated_constraints", [])
|
|
261
|
+
|
|
262
|
+
return VerifyResponse(
|
|
263
|
+
verified=bool(raw.get("verified")),
|
|
264
|
+
verdict=raw.get("verdict", "approved" if raw.get("verified") else "denied"),
|
|
265
|
+
proof_id=str(raw.get("proof_id", "")),
|
|
266
|
+
agent_id=str(raw.get("agent_id", "")),
|
|
267
|
+
action=str(raw.get("action", "")),
|
|
268
|
+
policy=raw.get("policy"),
|
|
269
|
+
reason=raw.get("reason"),
|
|
270
|
+
mode=raw.get("mode", "dpe"),
|
|
271
|
+
proof_status=raw.get("proof_status", "not_requested"),
|
|
272
|
+
evaluated_constraints=[
|
|
273
|
+
ConstraintResult(type=c.get("type", ""), result=c.get("result", "skip"), detail=c.get("detail"))
|
|
274
|
+
for c in constraints
|
|
275
|
+
],
|
|
276
|
+
verification=verification,
|
|
277
|
+
audit_id=str(raw.get("audit_id", "")),
|
|
278
|
+
latency_ms=float(raw.get("latency_ms", 0)),
|
|
279
|
+
timestamp=str(raw.get("timestamp", "")),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
async def _async_sleep(seconds: float):
|
|
284
|
+
"""Async sleep helper."""
|
|
285
|
+
import asyncio
|
|
286
|
+
await asyncio.sleep(seconds)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veritera
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Veritera Forge SDK — Verify AI agent decisions with cryptographic attestation
|
|
5
|
+
Author-email: Veritera AI <engineering@veritera.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://veritera.ai
|
|
8
|
+
Project-URL: Documentation, https://veritera.ai/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/VeriteraAI/veritera-website
|
|
10
|
+
Keywords: veritera,forge,ai,verification,agent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.25.0
|
|
14
|
+
Requires-Dist: cryptography>=41.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
veritera
|