truthlocks-maip 1.0.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.
@@ -0,0 +1,11 @@
1
+ bin/
2
+ *.exe
3
+ *.dll
4
+ *.so
5
+ *.dylib
6
+ node_modules/
7
+ dist/
8
+ __pycache__/
9
+ *.egg-info/
10
+ .env
11
+ coverage.out
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: truthlocks-maip
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the MAIP (Machine Agent Identity Protocol)
5
+ Project-URL: Homepage, https://github.com/truthlocks/maip
6
+ Project-URL: Repository, https://github.com/truthlocks/maip
7
+ Project-URL: Documentation, https://github.com/truthlocks/maip/tree/main/sdk/python
8
+ Author-email: "Truthlocks Inc." <engineering@truthlocks.com>
9
+ License-Expression: Apache-2.0
10
+ Keywords: agent-identity,ai-agents,delegation,maip,trust
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: httpx>=0.25.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # truthlocks-maip
26
+
27
+ Python SDK for the **MAIP (Machine Agent Identity Protocol)**.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install truthlocks-maip
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ from truthlocks_maip import (
39
+ MaipClient,
40
+ CreateAgentRequest,
41
+ CreateSessionRequest,
42
+ OfferDelegationRequest,
43
+ CheckGuardrailsRequest,
44
+ Scope,
45
+ )
46
+
47
+ client = MaipClient(api_key="your-api-key")
48
+ # For self-hosted: MaipClient(api_key="...", base_url="https://maip.your-company.com")
49
+
50
+ # Register an agent
51
+ agent = client.register_agent(CreateAgentRequest(
52
+ name="my-agent",
53
+ scope=Scope(actions=["read", "write"], resources=["documents/*"]),
54
+ public_key="base64-encoded-public-key",
55
+ ))
56
+
57
+ # Create a session
58
+ session = client.create_session(CreateSessionRequest(
59
+ agent_id=agent["id"],
60
+ ttl_seconds=3600,
61
+ ))
62
+
63
+ # Get trust score
64
+ trust = client.get_trust_score(agent["id"])
65
+ print(f"Trust level: {trust['level']}, score: {trust['score']}")
66
+
67
+ # Delegate trust
68
+ delegation = client.offer_delegation(OfferDelegationRequest(
69
+ from_agent_id=agent["id"],
70
+ to_agent_id="other-agent-id",
71
+ scope=Scope(actions=["read"], resources=["documents/*"]),
72
+ ttl_seconds=1800,
73
+ ))
74
+
75
+ # Check guardrails
76
+ result = client.check_guardrails(CheckGuardrailsRequest(
77
+ agent_id=agent["id"],
78
+ action="write",
79
+ resource_id="documents/report.pdf",
80
+ ))
81
+
82
+ if result["allowed"]:
83
+ pass # Proceed with the action
84
+ ```
85
+
86
+ ## Offline Bundle Verification
87
+
88
+ Verify receipt bundles without network access:
89
+
90
+ ```python
91
+ from truthlocks_maip import verify_bundle
92
+
93
+ result = verify_bundle(bundle)
94
+ if result.valid:
95
+ print(f"Verified {result.receipt_count} receipts")
96
+ else:
97
+ print("Bundle verification failed")
98
+ ```
99
+
100
+ ## API Reference
101
+
102
+ ### `MaipClient`
103
+
104
+ | Method | Description |
105
+ |---|---|
106
+ | `register_agent(request)` | Register a new agent identity |
107
+ | `list_agents(status?, offset?, limit?)` | List agents with optional filters |
108
+ | `get_agent(agent_id)` | Get an agent by ID |
109
+ | `suspend_agent(agent_id)` | Suspend an active agent |
110
+ | `revoke_agent(agent_id)` | Permanently revoke an agent |
111
+ | `create_session(request)` | Create an authenticated session |
112
+ | `terminate_session(session_id)` | Terminate a session |
113
+ | `get_trust_score(agent_id)` | Get the current trust score |
114
+ | `compute_trust_score(request)` | Compute a fresh trust score |
115
+ | `offer_delegation(request)` | Offer trust delegation |
116
+ | `accept_delegation(delegation_id)` | Accept a delegation |
117
+ | `execute_orchestration(request)` | Execute multi-agent orchestration |
118
+ | `check_guardrails(request)` | Check guardrails before an action |
119
+
120
+ ### Error Types
121
+
122
+ | Error | HTTP Status | Description |
123
+ |---|---|---|
124
+ | `MaipError` | any | Base error class |
125
+ | `UnauthorizedError` | 401 | Invalid or missing API key |
126
+ | `NotFoundError` | 404 | Resource not found |
127
+ | `LimitExceededError` | 429 | Rate limit or quota exceeded |
128
+ | `VerificationError` | n/a | Bundle/receipt verification failed |
129
+
130
+ ## License
131
+
132
+ Apache-2.0
@@ -0,0 +1,108 @@
1
+ # truthlocks-maip
2
+
3
+ Python SDK for the **MAIP (Machine Agent Identity Protocol)**.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install truthlocks-maip
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from truthlocks_maip import (
15
+ MaipClient,
16
+ CreateAgentRequest,
17
+ CreateSessionRequest,
18
+ OfferDelegationRequest,
19
+ CheckGuardrailsRequest,
20
+ Scope,
21
+ )
22
+
23
+ client = MaipClient(api_key="your-api-key")
24
+ # For self-hosted: MaipClient(api_key="...", base_url="https://maip.your-company.com")
25
+
26
+ # Register an agent
27
+ agent = client.register_agent(CreateAgentRequest(
28
+ name="my-agent",
29
+ scope=Scope(actions=["read", "write"], resources=["documents/*"]),
30
+ public_key="base64-encoded-public-key",
31
+ ))
32
+
33
+ # Create a session
34
+ session = client.create_session(CreateSessionRequest(
35
+ agent_id=agent["id"],
36
+ ttl_seconds=3600,
37
+ ))
38
+
39
+ # Get trust score
40
+ trust = client.get_trust_score(agent["id"])
41
+ print(f"Trust level: {trust['level']}, score: {trust['score']}")
42
+
43
+ # Delegate trust
44
+ delegation = client.offer_delegation(OfferDelegationRequest(
45
+ from_agent_id=agent["id"],
46
+ to_agent_id="other-agent-id",
47
+ scope=Scope(actions=["read"], resources=["documents/*"]),
48
+ ttl_seconds=1800,
49
+ ))
50
+
51
+ # Check guardrails
52
+ result = client.check_guardrails(CheckGuardrailsRequest(
53
+ agent_id=agent["id"],
54
+ action="write",
55
+ resource_id="documents/report.pdf",
56
+ ))
57
+
58
+ if result["allowed"]:
59
+ pass # Proceed with the action
60
+ ```
61
+
62
+ ## Offline Bundle Verification
63
+
64
+ Verify receipt bundles without network access:
65
+
66
+ ```python
67
+ from truthlocks_maip import verify_bundle
68
+
69
+ result = verify_bundle(bundle)
70
+ if result.valid:
71
+ print(f"Verified {result.receipt_count} receipts")
72
+ else:
73
+ print("Bundle verification failed")
74
+ ```
75
+
76
+ ## API Reference
77
+
78
+ ### `MaipClient`
79
+
80
+ | Method | Description |
81
+ |---|---|
82
+ | `register_agent(request)` | Register a new agent identity |
83
+ | `list_agents(status?, offset?, limit?)` | List agents with optional filters |
84
+ | `get_agent(agent_id)` | Get an agent by ID |
85
+ | `suspend_agent(agent_id)` | Suspend an active agent |
86
+ | `revoke_agent(agent_id)` | Permanently revoke an agent |
87
+ | `create_session(request)` | Create an authenticated session |
88
+ | `terminate_session(session_id)` | Terminate a session |
89
+ | `get_trust_score(agent_id)` | Get the current trust score |
90
+ | `compute_trust_score(request)` | Compute a fresh trust score |
91
+ | `offer_delegation(request)` | Offer trust delegation |
92
+ | `accept_delegation(delegation_id)` | Accept a delegation |
93
+ | `execute_orchestration(request)` | Execute multi-agent orchestration |
94
+ | `check_guardrails(request)` | Check guardrails before an action |
95
+
96
+ ### Error Types
97
+
98
+ | Error | HTTP Status | Description |
99
+ |---|---|---|
100
+ | `MaipError` | any | Base error class |
101
+ | `UnauthorizedError` | 401 | Invalid or missing API key |
102
+ | `NotFoundError` | 404 | Resource not found |
103
+ | `LimitExceededError` | 429 | Rate limit or quota exceeded |
104
+ | `VerificationError` | n/a | Bundle/receipt verification failed |
105
+
106
+ ## License
107
+
108
+ Apache-2.0
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "truthlocks-maip"
7
+ version = "1.0.0"
8
+ description = "Python SDK for the MAIP (Machine Agent Identity Protocol)"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Truthlocks Inc.", email = "engineering@truthlocks.com" },
14
+ ]
15
+ keywords = ["maip", "agent-identity", "trust", "delegation", "ai-agents"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "httpx>=0.25.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/truthlocks/maip"
34
+ Repository = "https://github.com/truthlocks/maip"
35
+ Documentation = "https://github.com/truthlocks/maip/tree/main/sdk/python"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/truthlocks_maip"]
@@ -0,0 +1,75 @@
1
+ # Copyright 2026 Truthlocks Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Python SDK for the MAIP (Machine Agent Identity Protocol)."""
5
+
6
+ from truthlocks_maip.client import MaipClient
7
+ from truthlocks_maip.errors import (
8
+ LimitExceededError,
9
+ MaipError,
10
+ NotFoundError,
11
+ UnauthorizedError,
12
+ VerificationError,
13
+ )
14
+ from truthlocks_maip.types import (
15
+ Agent,
16
+ AgentStatus,
17
+ Bundle,
18
+ CheckGuardrailsRequest,
19
+ ComputeTrustScoreRequest,
20
+ CreateAgentRequest,
21
+ CreateSessionRequest,
22
+ Delegation,
23
+ DelegationStatus,
24
+ ExecuteOrchestrationRequest,
25
+ GuardrailResult,
26
+ GuardrailViolation,
27
+ ListResponse,
28
+ OfferDelegationRequest,
29
+ Orchestration,
30
+ OrchestrationStatus,
31
+ Receipt,
32
+ Scope,
33
+ Session,
34
+ SessionStatus,
35
+ TrustFactor,
36
+ TrustLevel,
37
+ TrustScore,
38
+ )
39
+ from truthlocks_maip.verify import verify_bundle, verify_receipt_hash
40
+
41
+ __all__ = [
42
+ "MaipClient",
43
+ "MaipError",
44
+ "LimitExceededError",
45
+ "UnauthorizedError",
46
+ "NotFoundError",
47
+ "VerificationError",
48
+ "Agent",
49
+ "AgentStatus",
50
+ "Bundle",
51
+ "CheckGuardrailsRequest",
52
+ "ComputeTrustScoreRequest",
53
+ "CreateAgentRequest",
54
+ "CreateSessionRequest",
55
+ "Delegation",
56
+ "DelegationStatus",
57
+ "ExecuteOrchestrationRequest",
58
+ "GuardrailResult",
59
+ "GuardrailViolation",
60
+ "ListResponse",
61
+ "OfferDelegationRequest",
62
+ "Orchestration",
63
+ "OrchestrationStatus",
64
+ "Receipt",
65
+ "Scope",
66
+ "Session",
67
+ "SessionStatus",
68
+ "TrustFactor",
69
+ "TrustLevel",
70
+ "TrustScore",
71
+ "verify_bundle",
72
+ "verify_receipt_hash",
73
+ ]
74
+
75
+ __version__ = "0.1.0"
@@ -0,0 +1,269 @@
1
+ # Copyright 2026 Truthlocks Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """MAIP SDK client for interacting with the Machine Agent Identity Protocol API."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from dataclasses import asdict
10
+ from typing import Any, Optional
11
+ from urllib.parse import quote
12
+
13
+ import httpx
14
+
15
+ from truthlocks_maip.errors import (
16
+ LimitExceededError,
17
+ MaipError,
18
+ NotFoundError,
19
+ UnauthorizedError,
20
+ )
21
+ from truthlocks_maip.types import (
22
+ Agent,
23
+ AgentStatus,
24
+ CheckGuardrailsRequest,
25
+ ComputeTrustScoreRequest,
26
+ CreateAgentRequest,
27
+ CreateSessionRequest,
28
+ Delegation,
29
+ ExecuteOrchestrationRequest,
30
+ GuardrailResult,
31
+ ListResponse,
32
+ OfferDelegationRequest,
33
+ Orchestration,
34
+ Session,
35
+ TrustScore,
36
+ )
37
+
38
+ DEFAULT_BASE_URL = "https://api.truthlocks.com"
39
+ DEFAULT_TIMEOUT = 30.0
40
+
41
+
42
+ class MaipClient:
43
+ """MAIP SDK client.
44
+
45
+ Supports both the hosted Truthlocks API and self-hosted deployments
46
+ by configuring the ``base_url`` parameter.
47
+
48
+ Example::
49
+
50
+ from truthlocks_maip import MaipClient
51
+
52
+ client = MaipClient(api_key="your-api-key")
53
+
54
+ agent = client.register_agent(CreateAgentRequest(
55
+ name="my-agent",
56
+ scope=Scope(actions=["read"], resources=["*"]),
57
+ public_key="base64-encoded-public-key",
58
+ ))
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ api_key: str,
64
+ base_url: str = DEFAULT_BASE_URL,
65
+ timeout: float = DEFAULT_TIMEOUT,
66
+ ) -> None:
67
+ if not api_key:
68
+ raise MaipError("api_key is required")
69
+
70
+ self._base_url = base_url.rstrip("/")
71
+ self._client = httpx.Client(
72
+ base_url=self._base_url,
73
+ headers={
74
+ "Authorization": f"Bearer {api_key}",
75
+ "Content-Type": "application/json",
76
+ "Accept": "application/json",
77
+ "User-Agent": "truthlocks-maip-python/0.1.0",
78
+ },
79
+ timeout=timeout,
80
+ )
81
+
82
+ def close(self) -> None:
83
+ """Close the underlying HTTP client."""
84
+ self._client.close()
85
+
86
+ def __enter__(self) -> MaipClient:
87
+ return self
88
+
89
+ def __exit__(self, *args: Any) -> None:
90
+ self.close()
91
+
92
+ # -------------------------------------------------------------------------
93
+ # Agent Management
94
+ # -------------------------------------------------------------------------
95
+
96
+ def register_agent(self, request: CreateAgentRequest) -> dict[str, Any]:
97
+ """Register a new agent identity."""
98
+ return self._post("/v1/agents", self._to_api_dict(request))
99
+
100
+ def list_agents(
101
+ self,
102
+ status: Optional[AgentStatus] = None,
103
+ offset: Optional[int] = None,
104
+ limit: Optional[int] = None,
105
+ ) -> dict[str, Any]:
106
+ """List agents with optional filters."""
107
+ params: dict[str, Any] = {}
108
+ if status is not None:
109
+ params["status"] = status.value
110
+ if offset is not None:
111
+ params["offset"] = offset
112
+ if limit is not None:
113
+ params["limit"] = limit
114
+ return self._get("/v1/agents", params=params)
115
+
116
+ def get_agent(self, agent_id: str) -> dict[str, Any]:
117
+ """Retrieve a single agent by ID."""
118
+ return self._get(f"/v1/agents/{quote(agent_id, safe='')}")
119
+
120
+ def suspend_agent(self, agent_id: str) -> dict[str, Any]:
121
+ """Suspend an active agent."""
122
+ return self._post(
123
+ f"/v1/agents/{quote(agent_id, safe='')}/suspend", {}
124
+ )
125
+
126
+ def revoke_agent(self, agent_id: str) -> dict[str, Any]:
127
+ """Permanently revoke an agent identity."""
128
+ return self._post(
129
+ f"/v1/agents/{quote(agent_id, safe='')}/revoke", {}
130
+ )
131
+
132
+ # -------------------------------------------------------------------------
133
+ # Session Management
134
+ # -------------------------------------------------------------------------
135
+
136
+ def create_session(self, request: CreateSessionRequest) -> dict[str, Any]:
137
+ """Create a new authenticated session for an agent."""
138
+ return self._post("/v1/sessions", self._to_api_dict(request))
139
+
140
+ def terminate_session(self, session_id: str) -> dict[str, Any]:
141
+ """Terminate an active session."""
142
+ return self._post(
143
+ f"/v1/sessions/{quote(session_id, safe='')}/terminate", {}
144
+ )
145
+
146
+ # -------------------------------------------------------------------------
147
+ # Trust
148
+ # -------------------------------------------------------------------------
149
+
150
+ def get_trust_score(self, agent_id: str) -> dict[str, Any]:
151
+ """Get the current trust score for an agent."""
152
+ return self._get(
153
+ f"/v1/agents/{quote(agent_id, safe='')}/trust-score"
154
+ )
155
+
156
+ def compute_trust_score(
157
+ self, request: ComputeTrustScoreRequest
158
+ ) -> dict[str, Any]:
159
+ """Compute a fresh trust score for an agent."""
160
+ agent_id = request.agent_id
161
+ return self._post(
162
+ f"/v1/agents/{quote(agent_id, safe='')}/trust-score/compute",
163
+ self._to_api_dict(request),
164
+ )
165
+
166
+ # -------------------------------------------------------------------------
167
+ # Delegation
168
+ # -------------------------------------------------------------------------
169
+
170
+ def offer_delegation(
171
+ self, request: OfferDelegationRequest
172
+ ) -> dict[str, Any]:
173
+ """Offer a trust delegation from one agent to another."""
174
+ return self._post("/v1/delegations", self._to_api_dict(request))
175
+
176
+ def accept_delegation(self, delegation_id: str) -> dict[str, Any]:
177
+ """Accept an offered delegation."""
178
+ return self._post(
179
+ f"/v1/delegations/{quote(delegation_id, safe='')}/accept", {}
180
+ )
181
+
182
+ # -------------------------------------------------------------------------
183
+ # Orchestration
184
+ # -------------------------------------------------------------------------
185
+
186
+ def execute_orchestration(
187
+ self, request: ExecuteOrchestrationRequest
188
+ ) -> dict[str, Any]:
189
+ """Execute a multi-agent orchestration."""
190
+ return self._post("/v1/orchestrations", self._to_api_dict(request))
191
+
192
+ # -------------------------------------------------------------------------
193
+ # Guardrails
194
+ # -------------------------------------------------------------------------
195
+
196
+ def check_guardrails(
197
+ self, request: CheckGuardrailsRequest
198
+ ) -> dict[str, Any]:
199
+ """Check guardrails before performing an action."""
200
+ return self._post("/v1/guardrails/check", self._to_api_dict(request))
201
+
202
+ # -------------------------------------------------------------------------
203
+ # Internal helpers
204
+ # -------------------------------------------------------------------------
205
+
206
+ @staticmethod
207
+ def _to_api_dict(obj: Any) -> dict[str, Any]:
208
+ """Convert a dataclass to a dict with camelCase keys for the API."""
209
+ raw = asdict(obj)
210
+ return MaipClient._snake_to_camel_dict(raw)
211
+
212
+ @staticmethod
213
+ def _snake_to_camel(name: str) -> str:
214
+ parts = name.split("_")
215
+ return parts[0] + "".join(p.capitalize() for p in parts[1:])
216
+
217
+ @staticmethod
218
+ def _snake_to_camel_dict(d: dict[str, Any]) -> dict[str, Any]:
219
+ result: dict[str, Any] = {}
220
+ for key, value in d.items():
221
+ camel_key = MaipClient._snake_to_camel(key)
222
+ if isinstance(value, dict):
223
+ result[camel_key] = MaipClient._snake_to_camel_dict(value)
224
+ elif value is not None:
225
+ result[camel_key] = value
226
+ return result
227
+
228
+ def _get(
229
+ self, path: str, params: Optional[dict[str, Any]] = None
230
+ ) -> dict[str, Any]:
231
+ try:
232
+ response = self._client.get(path, params=params)
233
+ self._raise_for_status(response)
234
+ return response.json() # type: ignore[no-any-return]
235
+ except httpx.HTTPError as exc:
236
+ raise MaipError(f"Request failed: {exc}") from exc
237
+
238
+ def _post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
239
+ try:
240
+ response = self._client.post(path, json=body)
241
+ self._raise_for_status(response)
242
+ return response.json() # type: ignore[no-any-return]
243
+ except httpx.HTTPError as exc:
244
+ raise MaipError(f"Request failed: {exc}") from exc
245
+
246
+ @staticmethod
247
+ def _raise_for_status(response: httpx.Response) -> None:
248
+ if response.is_success:
249
+ return
250
+
251
+ try:
252
+ error_body = response.json()
253
+ message = error_body.get("message", f"HTTP {response.status_code}")
254
+ except Exception:
255
+ message = f"HTTP {response.status_code}"
256
+
257
+ status = response.status_code
258
+ if status == 401:
259
+ raise UnauthorizedError(message)
260
+ elif status == 404:
261
+ raise NotFoundError("Resource", message)
262
+ elif status == 429:
263
+ retry_after = response.headers.get("Retry-After")
264
+ raise LimitExceededError(
265
+ message,
266
+ retry_after_seconds=int(retry_after) if retry_after else None,
267
+ )
268
+ else:
269
+ raise MaipError(message, status_code=status)
@@ -0,0 +1,64 @@
1
+ # Copyright 2026 Truthlocks Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Error types for the MAIP SDK."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional
9
+
10
+
11
+ class MaipError(Exception):
12
+ """Base error class for all MAIP SDK errors."""
13
+
14
+ def __init__(
15
+ self,
16
+ message: str,
17
+ status_code: Optional[int] = None,
18
+ code: Optional[str] = None,
19
+ ) -> None:
20
+ super().__init__(message)
21
+ self.message = message
22
+ self.status_code = status_code
23
+ self.code = code
24
+
25
+
26
+ class LimitExceededError(MaipError):
27
+ """Raised when the caller exceeds a rate limit or quota."""
28
+
29
+ def __init__(
30
+ self,
31
+ message: str = "Rate limit exceeded",
32
+ retry_after_seconds: Optional[int] = None,
33
+ ) -> None:
34
+ super().__init__(message, status_code=429, code="LIMIT_EXCEEDED")
35
+ self.retry_after_seconds = retry_after_seconds
36
+
37
+
38
+ class UnauthorizedError(MaipError):
39
+ """Raised when the API key is missing, invalid, or lacks permission."""
40
+
41
+ def __init__(
42
+ self, message: str = "Unauthorized: invalid or missing API key"
43
+ ) -> None:
44
+ super().__init__(message, status_code=401, code="UNAUTHORIZED")
45
+
46
+
47
+ class NotFoundError(MaipError):
48
+ """Raised when a requested resource is not found."""
49
+
50
+ def __init__(self, resource: str, identifier: str) -> None:
51
+ super().__init__(
52
+ f"{resource} not found: {identifier}",
53
+ status_code=404,
54
+ code="NOT_FOUND",
55
+ )
56
+ self.resource = resource
57
+ self.identifier = identifier
58
+
59
+
60
+ class VerificationError(MaipError):
61
+ """Raised when bundle or receipt verification fails."""
62
+
63
+ def __init__(self, message: str) -> None:
64
+ super().__init__(message, code="VERIFICATION_FAILED")
@@ -0,0 +1,230 @@
1
+ # Copyright 2026 Truthlocks Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Data types for the MAIP SDK."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+ from typing import Any, Generic, Optional, TypeVar
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class AgentStatus(str, Enum):
16
+ ACTIVE = "active"
17
+ SUSPENDED = "suspended"
18
+ REVOKED = "revoked"
19
+
20
+
21
+ class SessionStatus(str, Enum):
22
+ ACTIVE = "active"
23
+ TERMINATED = "terminated"
24
+ EXPIRED = "expired"
25
+
26
+
27
+ class TrustLevel(str, Enum):
28
+ CRITICAL = "critical"
29
+ LOW = "low"
30
+ MEDIUM = "medium"
31
+ HIGH = "high"
32
+ VERIFIED = "verified"
33
+
34
+
35
+ class DelegationStatus(str, Enum):
36
+ OFFERED = "offered"
37
+ ACCEPTED = "accepted"
38
+ REJECTED = "rejected"
39
+ REVOKED = "revoked"
40
+ EXPIRED = "expired"
41
+
42
+
43
+ class OrchestrationStatus(str, Enum):
44
+ PENDING = "pending"
45
+ RUNNING = "running"
46
+ COMPLETED = "completed"
47
+ FAILED = "failed"
48
+ CANCELLED = "cancelled"
49
+
50
+
51
+ class ViolationSeverity(str, Enum):
52
+ INFO = "info"
53
+ WARNING = "warning"
54
+ ERROR = "error"
55
+ CRITICAL = "critical"
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class Scope:
60
+ """Permissions and boundaries for an agent."""
61
+ actions: list[str]
62
+ resources: list[str]
63
+ constraints: dict[str, str] = field(default_factory=dict)
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class Agent:
68
+ """A registered machine agent identity."""
69
+ id: str
70
+ name: str
71
+ status: AgentStatus
72
+ scope: Scope
73
+ public_key: str
74
+ metadata: dict[str, str]
75
+ created_at: str
76
+ updated_at: str
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class Session:
81
+ """An authenticated agent session."""
82
+ id: str
83
+ agent_id: str
84
+ status: SessionStatus
85
+ scope: Scope
86
+ expires_at: str
87
+ created_at: str
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class TrustFactor:
92
+ """A single factor contributing to a trust score."""
93
+ name: str
94
+ weight: float
95
+ value: float
96
+ description: str
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class TrustScore:
101
+ """Computed trust level for an agent."""
102
+ agent_id: str
103
+ score: float
104
+ level: TrustLevel
105
+ factors: list[TrustFactor]
106
+ computed_at: str
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class Delegation:
111
+ """Trust delegation from one agent to another."""
112
+ id: str
113
+ from_agent_id: str
114
+ to_agent_id: str
115
+ scope: Scope
116
+ status: DelegationStatus
117
+ expires_at: str
118
+ created_at: str
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class Orchestration:
123
+ """A multi-agent orchestration session."""
124
+ id: str
125
+ coordinator_agent_id: str
126
+ participant_agent_ids: list[str]
127
+ status: OrchestrationStatus
128
+ scope: Scope
129
+ created_at: str
130
+ result: Optional[dict[str, Any]] = None
131
+ completed_at: Optional[str] = None
132
+
133
+
134
+ @dataclass(frozen=True)
135
+ class Receipt:
136
+ """Cryptographic proof of an action taken by an agent."""
137
+ id: str
138
+ agent_id: str
139
+ session_id: str
140
+ action: str
141
+ resource_id: str
142
+ timestamp: str
143
+ signature: str
144
+ metadata: dict[str, str]
145
+
146
+
147
+ @dataclass(frozen=True)
148
+ class Bundle:
149
+ """Collection of receipts that can be verified offline."""
150
+ id: str
151
+ receipts: list[Receipt]
152
+ root_hash: str
153
+ signature: str
154
+ created_at: str
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class GuardrailViolation:
159
+ """A guardrail rule violation."""
160
+ rule: str
161
+ severity: ViolationSeverity
162
+ message: str
163
+
164
+
165
+ @dataclass(frozen=True)
166
+ class GuardrailResult:
167
+ """Outcome of a guardrails check."""
168
+ allowed: bool
169
+ violations: list[GuardrailViolation]
170
+ checked_at: str
171
+
172
+
173
+ @dataclass(frozen=True)
174
+ class ListResponse(Generic[T]):
175
+ """Paginated list response."""
176
+ items: list[T]
177
+ total: int
178
+ offset: int
179
+ limit: int
180
+
181
+
182
+ @dataclass(frozen=True)
183
+ class CreateAgentRequest:
184
+ """Options for creating an agent."""
185
+ name: str
186
+ scope: Scope
187
+ public_key: str
188
+ metadata: dict[str, str] = field(default_factory=dict)
189
+
190
+
191
+ @dataclass(frozen=True)
192
+ class CreateSessionRequest:
193
+ """Options for creating a session."""
194
+ agent_id: str
195
+ scope: Optional[Scope] = None
196
+ ttl_seconds: Optional[int] = None
197
+
198
+
199
+ @dataclass(frozen=True)
200
+ class ComputeTrustScoreRequest:
201
+ """Options for computing a trust score."""
202
+ agent_id: str
203
+ factors: Optional[list[dict[str, Any]]] = None
204
+
205
+
206
+ @dataclass(frozen=True)
207
+ class OfferDelegationRequest:
208
+ """Options for offering a delegation."""
209
+ from_agent_id: str
210
+ to_agent_id: str
211
+ scope: Scope
212
+ ttl_seconds: Optional[int] = None
213
+
214
+
215
+ @dataclass(frozen=True)
216
+ class ExecuteOrchestrationRequest:
217
+ """Options for executing an orchestration."""
218
+ coordinator_agent_id: str
219
+ participant_agent_ids: list[str]
220
+ scope: Scope
221
+ parameters: Optional[dict[str, Any]] = None
222
+
223
+
224
+ @dataclass(frozen=True)
225
+ class CheckGuardrailsRequest:
226
+ """Options for checking guardrails."""
227
+ agent_id: str
228
+ action: str
229
+ resource_id: str
230
+ context: Optional[dict[str, Any]] = None
@@ -0,0 +1,126 @@
1
+ # Copyright 2026 Truthlocks Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Offline bundle and receipt verification.
5
+
6
+ All functions in this module are pure and make no network calls.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ from dataclasses import dataclass
14
+
15
+ from truthlocks_maip.errors import VerificationError
16
+ from truthlocks_maip.types import Bundle, Receipt
17
+
18
+
19
+ def _sha256_hex(data: str) -> str:
20
+ """Compute SHA-256 hex digest of a string."""
21
+ return hashlib.sha256(data.encode("utf-8")).hexdigest()
22
+
23
+
24
+ def _hash_receipt(receipt: Receipt) -> str:
25
+ """Compute the canonical hash of a receipt for Merkle tree inclusion."""
26
+ canonical = json.dumps(
27
+ {
28
+ "action": receipt.action,
29
+ "agentId": receipt.agent_id,
30
+ "id": receipt.id,
31
+ "metadata": receipt.metadata,
32
+ "resourceId": receipt.resource_id,
33
+ "sessionId": receipt.session_id,
34
+ "signature": receipt.signature,
35
+ "timestamp": receipt.timestamp,
36
+ },
37
+ sort_keys=False,
38
+ separators=(",", ":"),
39
+ )
40
+ return _sha256_hex(canonical)
41
+
42
+
43
+ def _compute_merkle_root(leaf_hashes: list[str]) -> str:
44
+ """Compute the Merkle root from an ordered list of leaf hashes."""
45
+ if not leaf_hashes:
46
+ return _sha256_hex("")
47
+ if len(leaf_hashes) == 1:
48
+ return leaf_hashes[0]
49
+
50
+ level = list(leaf_hashes)
51
+ while len(level) > 1:
52
+ next_level: list[str] = []
53
+ for i in range(0, len(level), 2):
54
+ left = level[i]
55
+ right = level[i + 1] if i + 1 < len(level) else left
56
+ next_level.append(_sha256_hex(left + right))
57
+ level = next_level
58
+ return level[0]
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class VerifyBundleResult:
63
+ """Result of a bundle verification."""
64
+ valid: bool
65
+ computed_root_hash: str
66
+ declared_root_hash: str
67
+ receipt_count: int
68
+ receipt_hashes: list[str]
69
+
70
+
71
+ def verify_bundle(bundle: Bundle) -> VerifyBundleResult:
72
+ """Verify a receipt bundle offline.
73
+
74
+ Checks that the Merkle root hash matches the receipts in the bundle.
75
+ Does NOT verify cryptographic signatures against agent public keys
76
+ (that requires network access to retrieve keys).
77
+
78
+ Pure function: no network calls are made.
79
+
80
+ Args:
81
+ bundle: The bundle to verify.
82
+
83
+ Returns:
84
+ The verification result.
85
+
86
+ Raises:
87
+ VerificationError: If the bundle structure is invalid.
88
+ """
89
+ if not bundle.receipts or not isinstance(bundle.receipts, list):
90
+ raise VerificationError("Bundle has no receipts list")
91
+ if not bundle.root_hash:
92
+ raise VerificationError("Bundle has no root_hash")
93
+
94
+ receipt_hashes: list[str] = []
95
+ for receipt in bundle.receipts:
96
+ if not receipt.id or not receipt.agent_id or not receipt.timestamp:
97
+ raise VerificationError(
98
+ f"Receipt is missing required fields: {receipt}"
99
+ )
100
+ receipt_hashes.append(_hash_receipt(receipt))
101
+
102
+ computed_root_hash = _compute_merkle_root(receipt_hashes)
103
+
104
+ return VerifyBundleResult(
105
+ valid=computed_root_hash == bundle.root_hash,
106
+ computed_root_hash=computed_root_hash,
107
+ declared_root_hash=bundle.root_hash,
108
+ receipt_count=len(bundle.receipts),
109
+ receipt_hashes=receipt_hashes,
110
+ )
111
+
112
+
113
+ def verify_receipt_hash(receipt: Receipt, expected_hash: str) -> bool:
114
+ """Verify a single receipt hash against an expected value.
115
+
116
+ Useful for spot-checking individual receipts from a bundle.
117
+
118
+ Args:
119
+ receipt: The receipt to hash.
120
+ expected_hash: The expected SHA-256 hex hash.
121
+
122
+ Returns:
123
+ True if the computed hash matches the expected hash.
124
+ """
125
+ computed = _hash_receipt(receipt)
126
+ return computed == expected_hash