truststate 0.2.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.
- truststate-0.2.0/LICENSE +21 -0
- truststate-0.2.0/PKG-INFO +235 -0
- truststate-0.2.0/README.md +205 -0
- truststate-0.2.0/pyproject.toml +43 -0
- truststate-0.2.0/setup.cfg +4 -0
- truststate-0.2.0/tests/test_client.py +181 -0
- truststate-0.2.0/truststate/__init__.py +40 -0
- truststate-0.2.0/truststate/client.py +500 -0
- truststate-0.2.0/truststate/decorators.py +118 -0
- truststate-0.2.0/truststate/exceptions.py +18 -0
- truststate-0.2.0/truststate/middleware.py +127 -0
- truststate-0.2.0/truststate/types.py +112 -0
- truststate-0.2.0/truststate.egg-info/PKG-INFO +235 -0
- truststate-0.2.0/truststate.egg-info/SOURCES.txt +15 -0
- truststate-0.2.0/truststate.egg-info/dependency_links.txt +1 -0
- truststate-0.2.0/truststate.egg-info/requires.txt +9 -0
- truststate-0.2.0/truststate.egg-info/top_level.txt +1 -0
truststate-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MyreneBot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: truststate
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for TrustState compliance validation
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://trustchainlabs.com
|
|
7
|
+
Project-URL: Repository, https://github.com/MyreneBot/truststate-py
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/MyreneBot/truststate-py/issues
|
|
9
|
+
Keywords: compliance,AI governance,truststate,audit
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: dataclasses-json>=0.6
|
|
24
|
+
Provides-Extra: fastapi
|
|
25
|
+
Requires-Dist: starlette>=0.27; extra == "fastapi"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TrustState Python SDK
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/truststate/)
|
|
34
|
+
[](https://pypi.org/project/truststate/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
Python SDK for the [TrustState](https://truststate.apps.trustchainlabs.com) compliance API — validate, audit, and enforce compliance rules on any entity or data record. Built for financial services, AI governance, and regulated industries.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install truststate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.9+.
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import asyncio
|
|
51
|
+
from truststate import TrustStateClient
|
|
52
|
+
|
|
53
|
+
client = TrustStateClient(api_key="ts_your_api_key")
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
result = await client.check(
|
|
57
|
+
entity_type="SukukBond",
|
|
58
|
+
data={
|
|
59
|
+
"id": "BOND-001",
|
|
60
|
+
"issuerId": "ISS-001",
|
|
61
|
+
"currency": "MYR",
|
|
62
|
+
"faceValue": 5_000_000,
|
|
63
|
+
"maturityDate": "2030-06-01",
|
|
64
|
+
"status": "DRAFT",
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if result.passed:
|
|
69
|
+
print(f"✅ Passed — record ID: {result.record_id}")
|
|
70
|
+
else:
|
|
71
|
+
print(f"❌ Failed — {result.fail_reason} (step {result.failed_step})")
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Batch Writes
|
|
77
|
+
|
|
78
|
+
Submit multiple records in a single API call. Useful for feed-based pipelines.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
result = await client.check_batch(
|
|
82
|
+
items=[
|
|
83
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-001", ...}},
|
|
84
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-002", ...}},
|
|
85
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-003", ...}},
|
|
86
|
+
],
|
|
87
|
+
feed_label="core-banking-feed", # echoed on every item result
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
print(f"Accepted: {result.accepted}/{result.total}")
|
|
91
|
+
for item in result.results:
|
|
92
|
+
print(f" {item.entity_id}: {'✅' if item.passed else '❌'} {item.feed_label}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## BYOP Evidence (Oracle Data)
|
|
96
|
+
|
|
97
|
+
Attach oracle evidence to compliance checks — FX rates, KYC status, credit scores, sanctions screening.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Fetch evidence from registered oracle providers
|
|
101
|
+
fx = await client.fetch_fx_rate("MYR", "USD")
|
|
102
|
+
kyc = await client.fetch_kyc_status("actor-jasim")
|
|
103
|
+
score = await client.fetch_credit_score("actor-jasim")
|
|
104
|
+
|
|
105
|
+
# Submit with evidence attached
|
|
106
|
+
result = await client.check_with_evidence(
|
|
107
|
+
entity_type="SukukBond",
|
|
108
|
+
data={"id": "BOND-001", "issuerId": "ISS-001", "currency": "MYR", "faceValue": 5_000_000},
|
|
109
|
+
evidence=[fx, kyc, score],
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Mock Mode
|
|
114
|
+
|
|
115
|
+
Test without making any API calls. Useful for unit tests and local development.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
client = TrustStateClient(
|
|
119
|
+
api_key="any",
|
|
120
|
+
mock=True,
|
|
121
|
+
mock_pass_rate=0.8, # 80% of checks will pass
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
result = await client.check("SukukBond", {"id": "TEST-001", ...})
|
|
125
|
+
print(result.mock) # True
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Django / FastAPI Middleware
|
|
129
|
+
|
|
130
|
+
Automatically validate every incoming request body against TrustState policies.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# FastAPI
|
|
134
|
+
from truststate import TrustStateMiddleware
|
|
135
|
+
|
|
136
|
+
app.add_middleware(
|
|
137
|
+
TrustStateMiddleware,
|
|
138
|
+
api_key="ts_your_api_key",
|
|
139
|
+
entity_type="AgentResponse",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Django
|
|
143
|
+
MIDDLEWARE = [
|
|
144
|
+
"truststate.middleware.TrustStateMiddleware",
|
|
145
|
+
...
|
|
146
|
+
]
|
|
147
|
+
TRUSTSTATE_API_KEY = "ts_your_api_key"
|
|
148
|
+
TRUSTSTATE_ENTITY_TYPE = "AgentResponse"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## `@compliant` Decorator
|
|
152
|
+
|
|
153
|
+
Wrap any async function to automatically submit its return value for compliance checking.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from truststate import compliant, TrustStateClient
|
|
157
|
+
|
|
158
|
+
client = TrustStateClient(api_key="ts_your_api_key")
|
|
159
|
+
|
|
160
|
+
@compliant(client=client, entity_type="AgentResponse")
|
|
161
|
+
async def generate_response(prompt: str) -> dict:
|
|
162
|
+
return {"text": "Hello!", "score": 0.95}
|
|
163
|
+
|
|
164
|
+
result = await generate_response("What is TrustState?")
|
|
165
|
+
# result is a ComplianceResult — .passed, .record_id, etc.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Configuration
|
|
169
|
+
|
|
170
|
+
| Parameter | Type | Default | Description |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| `api_key` | `str` | required | Your TrustState API key |
|
|
173
|
+
| `base_url` | `str` | production URL | Override the API base URL |
|
|
174
|
+
| `default_schema_version` | `str \| None` | `None` | Schema version (auto-resolved if omitted) |
|
|
175
|
+
| `default_actor_id` | `str` | `""` | Actor ID for the audit trail |
|
|
176
|
+
| `mock` | `bool` | `False` | Enable mock mode (no HTTP calls) |
|
|
177
|
+
| `mock_pass_rate` | `float` | `1.0` | Pass probability in mock mode (0.0–1.0) |
|
|
178
|
+
| `timeout` | `int` | `30` | HTTP timeout in seconds |
|
|
179
|
+
|
|
180
|
+
## API Reference
|
|
181
|
+
|
|
182
|
+
### `check(entity_type, data, *, action, entity_id, schema_version, actor_id)`
|
|
183
|
+
|
|
184
|
+
Submit a single record for compliance checking.
|
|
185
|
+
|
|
186
|
+
Returns: `ComplianceResult`
|
|
187
|
+
|
|
188
|
+
### `check_batch(items, *, default_schema_version, default_actor_id, feed_label)`
|
|
189
|
+
|
|
190
|
+
Submit up to 500 records in a single call.
|
|
191
|
+
|
|
192
|
+
Returns: `BatchResult`
|
|
193
|
+
|
|
194
|
+
### `check_with_evidence(entity_type, data, evidence, *, action, entity_id, schema_version, actor_id)`
|
|
195
|
+
|
|
196
|
+
Submit a record with oracle evidence attached.
|
|
197
|
+
|
|
198
|
+
Returns: `ComplianceResult`
|
|
199
|
+
|
|
200
|
+
### `fetch_fx_rate(from_currency, to_currency, *, provider_id, max_age_seconds)`
|
|
201
|
+
### `fetch_kyc_status(subject_id, *, provider_id, max_age_seconds)`
|
|
202
|
+
### `fetch_credit_score(subject_id, *, provider_id, max_age_seconds)`
|
|
203
|
+
### `fetch_sanctions(subject_id, *, provider_id, max_age_seconds)`
|
|
204
|
+
|
|
205
|
+
Fetch oracle evidence items from registered providers.
|
|
206
|
+
|
|
207
|
+
Returns: `EvidenceItem`
|
|
208
|
+
|
|
209
|
+
### `verify(record_id, bearer_token)`
|
|
210
|
+
|
|
211
|
+
Retrieve an immutable compliance record from the ledger.
|
|
212
|
+
|
|
213
|
+
Returns: `dict`
|
|
214
|
+
|
|
215
|
+
## ComplianceResult
|
|
216
|
+
|
|
217
|
+
| Field | Type | Description |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `passed` | `bool` | True if all checks passed |
|
|
220
|
+
| `record_id` | `str \| None` | Immutable ledger record ID (only when passed) |
|
|
221
|
+
| `request_id` | `str` | Unique API request ID |
|
|
222
|
+
| `entity_id` | `str` | Entity ID that was submitted |
|
|
223
|
+
| `fail_reason` | `str \| None` | Human-readable failure reason |
|
|
224
|
+
| `failed_step` | `int \| None` | Step that failed (8=schema, 9=policy) |
|
|
225
|
+
| `feed_label` | `str \| None` | Feed label from batch request |
|
|
226
|
+
| `mock` | `bool` | True if synthesised in mock mode |
|
|
227
|
+
|
|
228
|
+
## Requirements
|
|
229
|
+
|
|
230
|
+
- Python 3.9+
|
|
231
|
+
- `httpx>=0.27`
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT © Trustchain Labs
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# TrustState Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/truststate/)
|
|
4
|
+
[](https://pypi.org/project/truststate/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Python SDK for the [TrustState](https://truststate.apps.trustchainlabs.com) compliance API — validate, audit, and enforce compliance rules on any entity or data record. Built for financial services, AI governance, and regulated industries.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install truststate
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.9+.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
from truststate import TrustStateClient
|
|
22
|
+
|
|
23
|
+
client = TrustStateClient(api_key="ts_your_api_key")
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
result = await client.check(
|
|
27
|
+
entity_type="SukukBond",
|
|
28
|
+
data={
|
|
29
|
+
"id": "BOND-001",
|
|
30
|
+
"issuerId": "ISS-001",
|
|
31
|
+
"currency": "MYR",
|
|
32
|
+
"faceValue": 5_000_000,
|
|
33
|
+
"maturityDate": "2030-06-01",
|
|
34
|
+
"status": "DRAFT",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if result.passed:
|
|
39
|
+
print(f"✅ Passed — record ID: {result.record_id}")
|
|
40
|
+
else:
|
|
41
|
+
print(f"❌ Failed — {result.fail_reason} (step {result.failed_step})")
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Batch Writes
|
|
47
|
+
|
|
48
|
+
Submit multiple records in a single API call. Useful for feed-based pipelines.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
result = await client.check_batch(
|
|
52
|
+
items=[
|
|
53
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-001", ...}},
|
|
54
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-002", ...}},
|
|
55
|
+
{"entity_type": "SukukBond", "data": {"id": "BOND-003", ...}},
|
|
56
|
+
],
|
|
57
|
+
feed_label="core-banking-feed", # echoed on every item result
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
print(f"Accepted: {result.accepted}/{result.total}")
|
|
61
|
+
for item in result.results:
|
|
62
|
+
print(f" {item.entity_id}: {'✅' if item.passed else '❌'} {item.feed_label}")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## BYOP Evidence (Oracle Data)
|
|
66
|
+
|
|
67
|
+
Attach oracle evidence to compliance checks — FX rates, KYC status, credit scores, sanctions screening.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Fetch evidence from registered oracle providers
|
|
71
|
+
fx = await client.fetch_fx_rate("MYR", "USD")
|
|
72
|
+
kyc = await client.fetch_kyc_status("actor-jasim")
|
|
73
|
+
score = await client.fetch_credit_score("actor-jasim")
|
|
74
|
+
|
|
75
|
+
# Submit with evidence attached
|
|
76
|
+
result = await client.check_with_evidence(
|
|
77
|
+
entity_type="SukukBond",
|
|
78
|
+
data={"id": "BOND-001", "issuerId": "ISS-001", "currency": "MYR", "faceValue": 5_000_000},
|
|
79
|
+
evidence=[fx, kyc, score],
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Mock Mode
|
|
84
|
+
|
|
85
|
+
Test without making any API calls. Useful for unit tests and local development.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
client = TrustStateClient(
|
|
89
|
+
api_key="any",
|
|
90
|
+
mock=True,
|
|
91
|
+
mock_pass_rate=0.8, # 80% of checks will pass
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
result = await client.check("SukukBond", {"id": "TEST-001", ...})
|
|
95
|
+
print(result.mock) # True
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Django / FastAPI Middleware
|
|
99
|
+
|
|
100
|
+
Automatically validate every incoming request body against TrustState policies.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# FastAPI
|
|
104
|
+
from truststate import TrustStateMiddleware
|
|
105
|
+
|
|
106
|
+
app.add_middleware(
|
|
107
|
+
TrustStateMiddleware,
|
|
108
|
+
api_key="ts_your_api_key",
|
|
109
|
+
entity_type="AgentResponse",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Django
|
|
113
|
+
MIDDLEWARE = [
|
|
114
|
+
"truststate.middleware.TrustStateMiddleware",
|
|
115
|
+
...
|
|
116
|
+
]
|
|
117
|
+
TRUSTSTATE_API_KEY = "ts_your_api_key"
|
|
118
|
+
TRUSTSTATE_ENTITY_TYPE = "AgentResponse"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## `@compliant` Decorator
|
|
122
|
+
|
|
123
|
+
Wrap any async function to automatically submit its return value for compliance checking.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from truststate import compliant, TrustStateClient
|
|
127
|
+
|
|
128
|
+
client = TrustStateClient(api_key="ts_your_api_key")
|
|
129
|
+
|
|
130
|
+
@compliant(client=client, entity_type="AgentResponse")
|
|
131
|
+
async def generate_response(prompt: str) -> dict:
|
|
132
|
+
return {"text": "Hello!", "score": 0.95}
|
|
133
|
+
|
|
134
|
+
result = await generate_response("What is TrustState?")
|
|
135
|
+
# result is a ComplianceResult — .passed, .record_id, etc.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
| Parameter | Type | Default | Description |
|
|
141
|
+
|---|---|---|---|
|
|
142
|
+
| `api_key` | `str` | required | Your TrustState API key |
|
|
143
|
+
| `base_url` | `str` | production URL | Override the API base URL |
|
|
144
|
+
| `default_schema_version` | `str \| None` | `None` | Schema version (auto-resolved if omitted) |
|
|
145
|
+
| `default_actor_id` | `str` | `""` | Actor ID for the audit trail |
|
|
146
|
+
| `mock` | `bool` | `False` | Enable mock mode (no HTTP calls) |
|
|
147
|
+
| `mock_pass_rate` | `float` | `1.0` | Pass probability in mock mode (0.0–1.0) |
|
|
148
|
+
| `timeout` | `int` | `30` | HTTP timeout in seconds |
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### `check(entity_type, data, *, action, entity_id, schema_version, actor_id)`
|
|
153
|
+
|
|
154
|
+
Submit a single record for compliance checking.
|
|
155
|
+
|
|
156
|
+
Returns: `ComplianceResult`
|
|
157
|
+
|
|
158
|
+
### `check_batch(items, *, default_schema_version, default_actor_id, feed_label)`
|
|
159
|
+
|
|
160
|
+
Submit up to 500 records in a single call.
|
|
161
|
+
|
|
162
|
+
Returns: `BatchResult`
|
|
163
|
+
|
|
164
|
+
### `check_with_evidence(entity_type, data, evidence, *, action, entity_id, schema_version, actor_id)`
|
|
165
|
+
|
|
166
|
+
Submit a record with oracle evidence attached.
|
|
167
|
+
|
|
168
|
+
Returns: `ComplianceResult`
|
|
169
|
+
|
|
170
|
+
### `fetch_fx_rate(from_currency, to_currency, *, provider_id, max_age_seconds)`
|
|
171
|
+
### `fetch_kyc_status(subject_id, *, provider_id, max_age_seconds)`
|
|
172
|
+
### `fetch_credit_score(subject_id, *, provider_id, max_age_seconds)`
|
|
173
|
+
### `fetch_sanctions(subject_id, *, provider_id, max_age_seconds)`
|
|
174
|
+
|
|
175
|
+
Fetch oracle evidence items from registered providers.
|
|
176
|
+
|
|
177
|
+
Returns: `EvidenceItem`
|
|
178
|
+
|
|
179
|
+
### `verify(record_id, bearer_token)`
|
|
180
|
+
|
|
181
|
+
Retrieve an immutable compliance record from the ledger.
|
|
182
|
+
|
|
183
|
+
Returns: `dict`
|
|
184
|
+
|
|
185
|
+
## ComplianceResult
|
|
186
|
+
|
|
187
|
+
| Field | Type | Description |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `passed` | `bool` | True if all checks passed |
|
|
190
|
+
| `record_id` | `str \| None` | Immutable ledger record ID (only when passed) |
|
|
191
|
+
| `request_id` | `str` | Unique API request ID |
|
|
192
|
+
| `entity_id` | `str` | Entity ID that was submitted |
|
|
193
|
+
| `fail_reason` | `str \| None` | Human-readable failure reason |
|
|
194
|
+
| `failed_step` | `int \| None` | Step that failed (8=schema, 9=policy) |
|
|
195
|
+
| `feed_label` | `str \| None` | Feed label from batch request |
|
|
196
|
+
| `mock` | `bool` | True if synthesised in mock mode |
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- Python 3.9+
|
|
201
|
+
- `httpx>=0.27`
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT © Trustchain Labs
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "truststate"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Python SDK for TrustState compliance validation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["compliance", "AI governance", "truststate", "audit"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"dataclasses-json>=0.6",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
fastapi = ["starlette>=0.27"]
|
|
31
|
+
dev = ["pytest>=7", "pytest-asyncio>=0.23"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://trustchainlabs.com"
|
|
35
|
+
Repository = "https://github.com/MyreneBot/truststate-py"
|
|
36
|
+
"Bug Tracker" = "https://github.com/MyreneBot/truststate-py/issues"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["."]
|
|
40
|
+
include = ["truststate*"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Unit tests for TrustStateClient.
|
|
2
|
+
|
|
3
|
+
Run with: pytest tests/test_client.py -v
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import pytest
|
|
10
|
+
from truststate import TrustStateClient
|
|
11
|
+
from truststate.types import ComplianceResult, BatchResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Helpers
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
def run(coro):
|
|
19
|
+
"""Run a coroutine in a new event loop (compatibility helper)."""
|
|
20
|
+
return asyncio.get_event_loop().run_until_complete(coro)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make_mock_client(mock_pass_rate: float = 1.0) -> TrustStateClient:
|
|
24
|
+
return TrustStateClient(
|
|
25
|
+
api_key="test-key",
|
|
26
|
+
mock=True,
|
|
27
|
+
mock_pass_rate=mock_pass_rate,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SAMPLE_DATA = {"responseText": "Hello", "confidenceScore": 0.9}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Tests
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
class TestMockMode:
|
|
39
|
+
"""Tests that run entirely in mock mode — no network calls required."""
|
|
40
|
+
|
|
41
|
+
def test_check_passes_in_mock_mode(self):
|
|
42
|
+
"""check() returns a passing ComplianceResult when mock_pass_rate=1.0."""
|
|
43
|
+
client = make_mock_client(mock_pass_rate=1.0)
|
|
44
|
+
result = run(client.check("AgentResponse", SAMPLE_DATA))
|
|
45
|
+
|
|
46
|
+
assert isinstance(result, ComplianceResult)
|
|
47
|
+
assert result.passed is True
|
|
48
|
+
assert result.record_id is not None
|
|
49
|
+
assert result.record_id.startswith("mock-rec-")
|
|
50
|
+
assert result.fail_reason is None
|
|
51
|
+
assert result.failed_step is None
|
|
52
|
+
assert result.mock is True
|
|
53
|
+
|
|
54
|
+
def test_check_batch_in_mock_mode(self):
|
|
55
|
+
"""check_batch() returns a BatchResult with mock=True for all results."""
|
|
56
|
+
client = make_mock_client(mock_pass_rate=1.0)
|
|
57
|
+
items = [
|
|
58
|
+
{"entity_type": "Tx", "data": {"amount": 100}},
|
|
59
|
+
{"entity_type": "Tx", "data": {"amount": 200}},
|
|
60
|
+
{"entity_type": "Tx", "data": {"amount": 300}},
|
|
61
|
+
]
|
|
62
|
+
batch = run(client.check_batch(items))
|
|
63
|
+
|
|
64
|
+
assert isinstance(batch, BatchResult)
|
|
65
|
+
assert batch.total == 3
|
|
66
|
+
assert batch.accepted == 3
|
|
67
|
+
assert batch.rejected == 0
|
|
68
|
+
assert len(batch.results) == 3
|
|
69
|
+
assert batch.mock is True
|
|
70
|
+
assert all(r.mock for r in batch.results)
|
|
71
|
+
assert all(r.passed for r in batch.results)
|
|
72
|
+
|
|
73
|
+
def test_mock_pass_rate_zero_always_fails(self):
|
|
74
|
+
"""mock_pass_rate=0.0 means every check() call returns passed=False."""
|
|
75
|
+
client = make_mock_client(mock_pass_rate=0.0)
|
|
76
|
+
|
|
77
|
+
# Run multiple times to confirm it is deterministic at 0.0
|
|
78
|
+
for _ in range(10):
|
|
79
|
+
result = run(client.check("AgentResponse", SAMPLE_DATA))
|
|
80
|
+
assert result.passed is False
|
|
81
|
+
assert result.record_id is None
|
|
82
|
+
assert result.fail_reason is not None
|
|
83
|
+
assert result.failed_step == 9
|
|
84
|
+
assert result.mock is True
|
|
85
|
+
|
|
86
|
+
def test_entity_id_auto_generated(self):
|
|
87
|
+
"""check() generates a unique entity_id when none is provided."""
|
|
88
|
+
client = make_mock_client()
|
|
89
|
+
result1 = run(client.check("AgentResponse", SAMPLE_DATA))
|
|
90
|
+
result2 = run(client.check("AgentResponse", SAMPLE_DATA))
|
|
91
|
+
|
|
92
|
+
# Both should have non-empty entity IDs
|
|
93
|
+
assert result1.entity_id
|
|
94
|
+
assert result2.entity_id
|
|
95
|
+
# They should be different (uuid4 generated)
|
|
96
|
+
assert result1.entity_id != result2.entity_id
|
|
97
|
+
|
|
98
|
+
def test_entity_id_preserved_when_provided(self):
|
|
99
|
+
"""check() preserves a caller-supplied entity_id."""
|
|
100
|
+
client = make_mock_client()
|
|
101
|
+
my_id = "my-stable-entity-id-123"
|
|
102
|
+
result = run(client.check("AgentResponse", SAMPLE_DATA, entity_id=my_id))
|
|
103
|
+
|
|
104
|
+
assert result.entity_id == my_id
|
|
105
|
+
|
|
106
|
+
def test_batch_partial_pass(self):
|
|
107
|
+
"""With mixed pass rate, accepted + rejected == total."""
|
|
108
|
+
import random
|
|
109
|
+
random.seed(42) # deterministic for the test
|
|
110
|
+
client = make_mock_client(mock_pass_rate=0.5)
|
|
111
|
+
items = [{"entity_type": "X", "data": {"v": i}} for i in range(20)]
|
|
112
|
+
batch = run(client.check_batch(items))
|
|
113
|
+
|
|
114
|
+
assert batch.total == 20
|
|
115
|
+
assert batch.accepted + batch.rejected == batch.total
|
|
116
|
+
assert len(batch.results) == 20
|
|
117
|
+
|
|
118
|
+
def test_check_batch_auto_generates_entity_ids(self):
|
|
119
|
+
"""check_batch() auto-generates entity_id for items that omit it."""
|
|
120
|
+
client = make_mock_client()
|
|
121
|
+
items = [
|
|
122
|
+
{"entity_type": "T", "data": {"x": 1}}, # no entity_id
|
|
123
|
+
{"entity_type": "T", "data": {"x": 2}, "entity_id": "explicit-id"},
|
|
124
|
+
]
|
|
125
|
+
batch = run(client.check_batch(items))
|
|
126
|
+
|
|
127
|
+
assert batch.results[0].entity_id # auto-generated, non-empty
|
|
128
|
+
assert batch.results[1].entity_id == "explicit-id"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestErrorHandling:
|
|
132
|
+
"""Tests for TrustStateError and validation."""
|
|
133
|
+
|
|
134
|
+
def test_trust_state_error_attributes(self):
|
|
135
|
+
"""TrustStateError carries message and status_code."""
|
|
136
|
+
from truststate.exceptions import TrustStateError
|
|
137
|
+
err = TrustStateError("something broke", 422)
|
|
138
|
+
assert err.message == "something broke"
|
|
139
|
+
assert err.status_code == 422
|
|
140
|
+
assert "422" in repr(err)
|
|
141
|
+
|
|
142
|
+
def test_compliant_decorator_raises_on_fail(self):
|
|
143
|
+
"""@compliant with on_fail='raise' raises TrustStateError when check fails."""
|
|
144
|
+
from truststate import compliant
|
|
145
|
+
from truststate.exceptions import TrustStateError
|
|
146
|
+
|
|
147
|
+
client = make_mock_client(mock_pass_rate=0.0)
|
|
148
|
+
|
|
149
|
+
@compliant(client, entity_type="Test", on_fail="raise")
|
|
150
|
+
async def my_fn():
|
|
151
|
+
return {"value": 1}
|
|
152
|
+
|
|
153
|
+
with pytest.raises(TrustStateError):
|
|
154
|
+
run(my_fn())
|
|
155
|
+
|
|
156
|
+
def test_compliant_decorator_returns_none_on_fail(self):
|
|
157
|
+
"""@compliant with on_fail='return_none' returns None when check fails."""
|
|
158
|
+
from truststate import compliant
|
|
159
|
+
|
|
160
|
+
client = make_mock_client(mock_pass_rate=0.0)
|
|
161
|
+
|
|
162
|
+
@compliant(client, entity_type="Test", on_fail="return_none")
|
|
163
|
+
async def my_fn():
|
|
164
|
+
return {"value": 1}
|
|
165
|
+
|
|
166
|
+
result = run(my_fn())
|
|
167
|
+
assert result is None
|
|
168
|
+
|
|
169
|
+
def test_compliant_passes_through_value_on_success(self):
|
|
170
|
+
"""@compliant returns the original value when check passes."""
|
|
171
|
+
from truststate import compliant
|
|
172
|
+
|
|
173
|
+
client = make_mock_client(mock_pass_rate=1.0)
|
|
174
|
+
expected = {"responseText": "hi", "confidenceScore": 0.9}
|
|
175
|
+
|
|
176
|
+
@compliant(client, entity_type="Test", on_fail="raise")
|
|
177
|
+
async def my_fn():
|
|
178
|
+
return expected
|
|
179
|
+
|
|
180
|
+
result = run(my_fn())
|
|
181
|
+
assert result == expected
|