thessor 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.
@@ -0,0 +1,7 @@
1
+ .env
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .pytest_cache/
6
+ *.egg-info/
7
+ .DS_Store
thessor-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,358 @@
1
+ Metadata-Version: 2.4
2
+ Name: thessor
3
+ Version: 0.2.0
4
+ Summary: The Thessor SDK — cryptographic AI decision accountability
5
+ Project-URL: Homepage, https://thessor.com
6
+ Project-URL: Repository, https://github.com/SOLOMONTHESSOR/thessor-api
7
+ Author-email: "Thessor, Inc." <solomon@thessor.com>
8
+ License: Proprietary
9
+ Requires-Python: >=3.9
10
+ Requires-Dist: cryptography>=41.0.0
11
+ Requires-Dist: httpx>=0.24.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Thessor Python SDK
15
+
16
+ Cryptographic accountability for AI decisions. Seals decisions into
17
+ tamper-evident, Ed25519-signed envelopes and verifies them — without ever
18
+ requiring you to send PHI to Thessor.
19
+
20
+ **v0.2.0** · Python ≥ 3.11 · zero third-party dependencies
21
+
22
+ ## Install
23
+
24
+ ```
25
+ pip install thessor
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```python
31
+ from thessor import Client
32
+
33
+ client = Client(api_key="thsr_...")
34
+
35
+ result = client.seal(
36
+ decision_id="dec-001",
37
+ model_id="cnds-classifier-v3",
38
+ model_version="3.2.1",
39
+ timestamp="2026-06-19T10:00:00Z",
40
+ input_hash="sha256:...",
41
+ output={"label": "approved"},
42
+ confidence_score=0.97,
43
+ policy_version="policy-2026-Q2",
44
+ payload={"finding": "..."},
45
+ )
46
+ print(result.envelope_id, result.verify_url)
47
+
48
+ verification = client.verify(result.envelope_id)
49
+ assert verification.valid
50
+ ```
51
+
52
+ ## PHI never has to leave your environment
53
+
54
+ Use `hash_patient_id()` to turn a patient ID into a one-way SHA-256 hash
55
+ before it ever leaves your systems, and pass the hash as `patient_id_hash`
56
+ in `attestations`:
57
+
58
+ ```python
59
+ from thessor import hash_patient_id
60
+
61
+ result = client.seal(
62
+ ...,
63
+ attestations={"patient_id_hash": hash_patient_id(patient.mrn)},
64
+ )
65
+ ```
66
+
67
+ Thessor stores and seals only the hash. `canonicalize()` and `sha256_hex()`
68
+ are also exposed for fully offline, trustless verification with no API call
69
+ (`client.verify_direct` supports this flow).
70
+
71
+ ---
72
+
73
+ ## API reference
74
+
75
+ ### Sealing decisions
76
+
77
+ #### `client.seal(...) → SealResult`
78
+
79
+ Seal a decision into a tamper-evident, Ed25519-signed envelope.
80
+
81
+ ```python
82
+ result = client.seal(
83
+ decision_id="dec-001", # caller-supplied stable ID
84
+ model_id="cnds-classifier-v3",
85
+ model_version="3.2.1",
86
+ timestamp="2026-06-19T10:00:00Z",
87
+ input_hash="sha256:abc123", # SHA-256 of the canonical input
88
+ output={"label": "approved"},
89
+ confidence_score=0.97,
90
+ policy_version="policy-2026-Q2",
91
+ payload={"finding": "..."}, # extra context (not sealed, not sent)
92
+ attestations={ # optional: sealed regulatory metadata
93
+ "regulatory_frameworks": ["fda_pccp", "eu_ai_act_high_risk"],
94
+ "clinician_npi": "1234567890",
95
+ "patient_id_hash": hash_patient_id(patient.mrn),
96
+ },
97
+ sync=True, # default: block until envelope is written
98
+ )
99
+ # result.envelope_id, result.canonical_hash, result.signature, result.verify_url
100
+ ```
101
+
102
+ `sync=False` queues the seal and returns immediately (status `"pending"`).
103
+ Poll with `get_seal_status(seal_id)` or use `seal_and_wait()`.
104
+
105
+ #### `client.seal_with_rationale(...) → SealWithRationaleResult`
106
+
107
+ Seal a decision and immediately seal its rationale record in one call.
108
+
109
+ ```python
110
+ result = client.seal_with_rationale(
111
+ # all seal() fields:
112
+ decision_id="dec-002",
113
+ model_id="cnds-classifier-v3",
114
+ model_version="3.2.1",
115
+ timestamp="2026-06-19T10:00:00Z",
116
+ input_hash="sha256:abc123",
117
+ output={"label": "approved"},
118
+ confidence_score=0.97,
119
+ policy_version="policy-2026-Q2",
120
+ payload={},
121
+ # rationale fields:
122
+ confidence_distribution={"approved": 0.97, "denied": 0.03},
123
+ salient_inputs={"age": 0.42, "risk_score": 0.81},
124
+ policy_logic_applied={"rule": "threshold_0.85", "version": "v3"},
125
+ human_oversight_action="clinician_confirmed",
126
+ human_operator_id_hash=sha256_hex(b"npi:1234567890"),
127
+ )
128
+ print(result.envelope_id) # shortcut to result.seal.envelope_id
129
+ print(result.rationale_id)
130
+ print(result.rationale_hash)
131
+ ```
132
+
133
+ #### `client.seal_agent_decision(...) → dict`
134
+
135
+ Seal an agent tool call without setting up `ThessorMCPMiddleware` directly.
136
+ Works with any agent framework (LangChain, AutoGen, custom orchestrators).
137
+ Raw inputs and outputs are never sent — only their SHA-256 hashes.
138
+
139
+ ```python
140
+ envelope = client.seal_agent_decision(
141
+ tool_name="search_formulary",
142
+ tool_input={"drug_name": "metformin", "plan_id": "BCBS-HMO"},
143
+ tool_output={"formulary_match": True, "tier": 2},
144
+ model_id="gpt-4o",
145
+ model_version="2024-11-20",
146
+ agent_id_hash=sha256_hex(b"agent:workflow-v1"),
147
+ confidence=0.95,
148
+ policy_version="formulary-policy-v2",
149
+ parent_decision_id=prior_envelope_id, # chains to parent decision
150
+ )
151
+ print(envelope["envelope_id"])
152
+ ```
153
+
154
+ #### `client.seal_and_wait(...) → SealResult`
155
+
156
+ Queue an async seal and block until it completes or times out.
157
+
158
+ ```python
159
+ result = client.seal_and_wait(
160
+ poll_interval=0.5,
161
+ timeout=30.0,
162
+ decision_id="dec-003",
163
+ ... # same kwargs as seal()
164
+ )
165
+ ```
166
+
167
+ #### `client.get_seal_status(seal_id) → SealResult`
168
+
169
+ Poll the status of an async seal (`seal(sync=False)`).
170
+
171
+ ---
172
+
173
+ ### Verification
174
+
175
+ #### `client.verify(envelope_id) → VerifyResult`
176
+
177
+ Fetch verification metadata for a sealed envelope.
178
+
179
+ ```python
180
+ v = client.verify(result.envelope_id)
181
+ print(v.valid, v.canonical_hash, v.regulatory_context)
182
+ ```
183
+
184
+ #### `client.verify_with_payload(envelope_id, payload) → VerifyResult`
185
+
186
+ Verify that a payload matches the sealed envelope's hash and signature.
187
+
188
+ #### `client.verify_direct(payload, signature_hex, public_key_hex) → VerifyResult`
189
+
190
+ Fully trustless, offline verification — no envelope lookup, no DB. Useful
191
+ for auditors who only have the envelope contents and the published Thessor
192
+ public key.
193
+
194
+ ---
195
+
196
+ ### Outcomes and consequence chains
197
+
198
+ #### `client.seal_outcome(outcome_id, model_id, outcome_type, payload) → SealResult`
199
+
200
+ Seal an outcome envelope to later link to a decision.
201
+
202
+ #### `client.link(decision_envelope_id, outcome_envelope_id, causal_claim) → dict`
203
+
204
+ Causally link a decision envelope to an outcome envelope.
205
+
206
+ #### `client.get_chain(decision_envelope_id) → ChainResult`
207
+
208
+ Fetch the full consequence chain (decision + linked outcomes) for a
209
+ decision envelope.
210
+
211
+ ---
212
+
213
+ ### Population attestation
214
+
215
+ #### `client.attest_population(...) → dict`
216
+
217
+ Post a sealed aggregate performance attestation for a model version over a
218
+ defined time window. Required for GMLP, EU AI Act post-market surveillance,
219
+ ISO 42001, and FDA PCCP total-lifecycle monitoring.
220
+
221
+ ```python
222
+ attestation = client.attest_population(
223
+ model_version="ct-lung-v4.1.0",
224
+ period_start="2026-01-01T00:00:00Z",
225
+ period_end="2026-03-31T23:59:59Z",
226
+ site_identifier_hash=sha256_hex(b"site:hospital-a"),
227
+ decision_count=1000,
228
+ confirmed_count=400,
229
+ true_positive_count=180,
230
+ false_positive_count=20,
231
+ true_negative_count=190,
232
+ false_negative_count=10,
233
+ drift_flag=False,
234
+ regulatory_frameworks=["fda_pccp", "gmlp_principle_7"],
235
+ )
236
+ print(attestation["attestation_id"])
237
+ print(attestation["sensitivity"]) # 0.947
238
+ print(attestation["specificity"]) # 0.905
239
+ ```
240
+
241
+ ---
242
+
243
+ ### Offline audit export
244
+
245
+ #### `client.export_packet(decision_id) → dict`
246
+
247
+ Fetch the signed, self-verifying offline audit packet for a decision — a
248
+ single JSON bundle containing the decision envelope, replay record, rationale,
249
+ consequence chain, population context, and Merkle checkpoint. Every nested
250
+ record carries its own signature; the `export_signature` seals the entire
251
+ bundle. A regulator can verify it offline without Thessor infrastructure.
252
+
253
+ ```python
254
+ packet = client.export_packet(envelope_id)
255
+ # packet["export_signature"] seals every field
256
+ ```
257
+
258
+ #### `client.export_to_file(decision_id, filepath) → dict`
259
+
260
+ Fetch the audit packet and write it to `filepath` as pretty-printed JSON.
261
+ Returns the packet dict as well.
262
+
263
+ ```python
264
+ packet = client.export_to_file(envelope_id, "/tmp/audit-dec-001.json")
265
+ ```
266
+
267
+ ---
268
+
269
+ ### MCP middleware
270
+
271
+ For agent frameworks using the Model Context Protocol, use
272
+ `ThessorMCPMiddleware` to seal every tool call automatically:
273
+
274
+ ```python
275
+ from thessor import ThessorMCPMiddleware, sha256_hex
276
+
277
+ middleware = ThessorMCPMiddleware(
278
+ thessor_client=client,
279
+ model_id="claude-sonnet-4-6",
280
+ model_version="20250620",
281
+ agent_id_hash=sha256_hex(b"agent:my-workflow-v1"),
282
+ )
283
+
284
+ # Seal a tool call manually:
285
+ envelope = middleware.seal_tool_call(
286
+ "lookup_icd10",
287
+ tool_input={"description": "type 2 diabetes"},
288
+ tool_output={"code": "E11", "description": "Type 2 diabetes mellitus"},
289
+ confidence=0.99,
290
+ parent_decision_id=prior_envelope_id,
291
+ )
292
+
293
+ # Or use the decorator to seal every call automatically:
294
+ @middleware.seal_mcp_tool
295
+ def search_formulary(tool_input: dict) -> dict:
296
+ return {"tier": 2, "covered": True}
297
+
298
+ result = search_formulary({"drug_name": "metformin"})
299
+ # → tool runs, output sealed, result returned unchanged
300
+ ```
301
+
302
+ `seal_tool_call_async()` is also available for async contexts.
303
+
304
+ ---
305
+
306
+ ## Error handling
307
+
308
+ All API errors raise a subclass of `ThessorError`:
309
+
310
+ ```python
311
+ from thessor import AuthError, ValidationError, RateLimitError, NotFoundError
312
+
313
+ try:
314
+ client.seal(...)
315
+ except ValidationError as exc:
316
+ print(exc.field_errors) # structured per-field validation errors
317
+ except AuthError:
318
+ print("check your API key")
319
+ except RateLimitError:
320
+ print("back off and retry later")
321
+ except NotFoundError:
322
+ print("envelope not found")
323
+ ```
324
+
325
+ 429 and 5xx responses are retried automatically with exponential backoff
326
+ (0.5 s, 1 s, 2 s). 4xx errors are raised immediately.
327
+
328
+ ---
329
+
330
+ ## Changelog
331
+
332
+ ### 0.2.0
333
+ - `Client.seal_with_rationale()` — seal + rationale in one call
334
+ - `Client.seal_agent_decision()` — agent sealing without MCP setup
335
+ - `Client.attest_population()` — population performance attestation
336
+ - `ThessorMCPMiddleware` — MCP-native agent decision sealing
337
+ - Model classes (`SealResult`, `SealWithRationaleResult`, `VerifyResult`,
338
+ `ChainResult`) now exported from the top-level package
339
+ - Version bump to 0.2.0
340
+
341
+ ### 0.1.0
342
+ - Initial release: `Client`, `FHIRConnector`, `DICOMSRConnector`,
343
+ `ATNAConnector`, `HL7Connector`, PHI-safe hashing utilities,
344
+ `export_packet`, `export_to_file`
345
+
346
+ ---
347
+
348
+ ## Releasing
349
+
350
+ To publish a new version to PyPI:
351
+
352
+ 1. Bump version in `sdk/pyproject.toml`, `sdk/thessor/__init__.py`, and `sdk/VERSION`
353
+ 2. `git add sdk/`
354
+ 3. `git commit -m "release: SDK v0.x.x"`
355
+ 4. `git tag sdk-v0.x.x`
356
+ 5. `git push origin main --tags`
357
+
358
+ GitHub Actions will build and push to PyPI automatically.
@@ -0,0 +1,345 @@
1
+ # Thessor Python SDK
2
+
3
+ Cryptographic accountability for AI decisions. Seals decisions into
4
+ tamper-evident, Ed25519-signed envelopes and verifies them — without ever
5
+ requiring you to send PHI to Thessor.
6
+
7
+ **v0.2.0** · Python ≥ 3.11 · zero third-party dependencies
8
+
9
+ ## Install
10
+
11
+ ```
12
+ pip install thessor
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```python
18
+ from thessor import Client
19
+
20
+ client = Client(api_key="thsr_...")
21
+
22
+ result = client.seal(
23
+ decision_id="dec-001",
24
+ model_id="cnds-classifier-v3",
25
+ model_version="3.2.1",
26
+ timestamp="2026-06-19T10:00:00Z",
27
+ input_hash="sha256:...",
28
+ output={"label": "approved"},
29
+ confidence_score=0.97,
30
+ policy_version="policy-2026-Q2",
31
+ payload={"finding": "..."},
32
+ )
33
+ print(result.envelope_id, result.verify_url)
34
+
35
+ verification = client.verify(result.envelope_id)
36
+ assert verification.valid
37
+ ```
38
+
39
+ ## PHI never has to leave your environment
40
+
41
+ Use `hash_patient_id()` to turn a patient ID into a one-way SHA-256 hash
42
+ before it ever leaves your systems, and pass the hash as `patient_id_hash`
43
+ in `attestations`:
44
+
45
+ ```python
46
+ from thessor import hash_patient_id
47
+
48
+ result = client.seal(
49
+ ...,
50
+ attestations={"patient_id_hash": hash_patient_id(patient.mrn)},
51
+ )
52
+ ```
53
+
54
+ Thessor stores and seals only the hash. `canonicalize()` and `sha256_hex()`
55
+ are also exposed for fully offline, trustless verification with no API call
56
+ (`client.verify_direct` supports this flow).
57
+
58
+ ---
59
+
60
+ ## API reference
61
+
62
+ ### Sealing decisions
63
+
64
+ #### `client.seal(...) → SealResult`
65
+
66
+ Seal a decision into a tamper-evident, Ed25519-signed envelope.
67
+
68
+ ```python
69
+ result = client.seal(
70
+ decision_id="dec-001", # caller-supplied stable ID
71
+ model_id="cnds-classifier-v3",
72
+ model_version="3.2.1",
73
+ timestamp="2026-06-19T10:00:00Z",
74
+ input_hash="sha256:abc123", # SHA-256 of the canonical input
75
+ output={"label": "approved"},
76
+ confidence_score=0.97,
77
+ policy_version="policy-2026-Q2",
78
+ payload={"finding": "..."}, # extra context (not sealed, not sent)
79
+ attestations={ # optional: sealed regulatory metadata
80
+ "regulatory_frameworks": ["fda_pccp", "eu_ai_act_high_risk"],
81
+ "clinician_npi": "1234567890",
82
+ "patient_id_hash": hash_patient_id(patient.mrn),
83
+ },
84
+ sync=True, # default: block until envelope is written
85
+ )
86
+ # result.envelope_id, result.canonical_hash, result.signature, result.verify_url
87
+ ```
88
+
89
+ `sync=False` queues the seal and returns immediately (status `"pending"`).
90
+ Poll with `get_seal_status(seal_id)` or use `seal_and_wait()`.
91
+
92
+ #### `client.seal_with_rationale(...) → SealWithRationaleResult`
93
+
94
+ Seal a decision and immediately seal its rationale record in one call.
95
+
96
+ ```python
97
+ result = client.seal_with_rationale(
98
+ # all seal() fields:
99
+ decision_id="dec-002",
100
+ model_id="cnds-classifier-v3",
101
+ model_version="3.2.1",
102
+ timestamp="2026-06-19T10:00:00Z",
103
+ input_hash="sha256:abc123",
104
+ output={"label": "approved"},
105
+ confidence_score=0.97,
106
+ policy_version="policy-2026-Q2",
107
+ payload={},
108
+ # rationale fields:
109
+ confidence_distribution={"approved": 0.97, "denied": 0.03},
110
+ salient_inputs={"age": 0.42, "risk_score": 0.81},
111
+ policy_logic_applied={"rule": "threshold_0.85", "version": "v3"},
112
+ human_oversight_action="clinician_confirmed",
113
+ human_operator_id_hash=sha256_hex(b"npi:1234567890"),
114
+ )
115
+ print(result.envelope_id) # shortcut to result.seal.envelope_id
116
+ print(result.rationale_id)
117
+ print(result.rationale_hash)
118
+ ```
119
+
120
+ #### `client.seal_agent_decision(...) → dict`
121
+
122
+ Seal an agent tool call without setting up `ThessorMCPMiddleware` directly.
123
+ Works with any agent framework (LangChain, AutoGen, custom orchestrators).
124
+ Raw inputs and outputs are never sent — only their SHA-256 hashes.
125
+
126
+ ```python
127
+ envelope = client.seal_agent_decision(
128
+ tool_name="search_formulary",
129
+ tool_input={"drug_name": "metformin", "plan_id": "BCBS-HMO"},
130
+ tool_output={"formulary_match": True, "tier": 2},
131
+ model_id="gpt-4o",
132
+ model_version="2024-11-20",
133
+ agent_id_hash=sha256_hex(b"agent:workflow-v1"),
134
+ confidence=0.95,
135
+ policy_version="formulary-policy-v2",
136
+ parent_decision_id=prior_envelope_id, # chains to parent decision
137
+ )
138
+ print(envelope["envelope_id"])
139
+ ```
140
+
141
+ #### `client.seal_and_wait(...) → SealResult`
142
+
143
+ Queue an async seal and block until it completes or times out.
144
+
145
+ ```python
146
+ result = client.seal_and_wait(
147
+ poll_interval=0.5,
148
+ timeout=30.0,
149
+ decision_id="dec-003",
150
+ ... # same kwargs as seal()
151
+ )
152
+ ```
153
+
154
+ #### `client.get_seal_status(seal_id) → SealResult`
155
+
156
+ Poll the status of an async seal (`seal(sync=False)`).
157
+
158
+ ---
159
+
160
+ ### Verification
161
+
162
+ #### `client.verify(envelope_id) → VerifyResult`
163
+
164
+ Fetch verification metadata for a sealed envelope.
165
+
166
+ ```python
167
+ v = client.verify(result.envelope_id)
168
+ print(v.valid, v.canonical_hash, v.regulatory_context)
169
+ ```
170
+
171
+ #### `client.verify_with_payload(envelope_id, payload) → VerifyResult`
172
+
173
+ Verify that a payload matches the sealed envelope's hash and signature.
174
+
175
+ #### `client.verify_direct(payload, signature_hex, public_key_hex) → VerifyResult`
176
+
177
+ Fully trustless, offline verification — no envelope lookup, no DB. Useful
178
+ for auditors who only have the envelope contents and the published Thessor
179
+ public key.
180
+
181
+ ---
182
+
183
+ ### Outcomes and consequence chains
184
+
185
+ #### `client.seal_outcome(outcome_id, model_id, outcome_type, payload) → SealResult`
186
+
187
+ Seal an outcome envelope to later link to a decision.
188
+
189
+ #### `client.link(decision_envelope_id, outcome_envelope_id, causal_claim) → dict`
190
+
191
+ Causally link a decision envelope to an outcome envelope.
192
+
193
+ #### `client.get_chain(decision_envelope_id) → ChainResult`
194
+
195
+ Fetch the full consequence chain (decision + linked outcomes) for a
196
+ decision envelope.
197
+
198
+ ---
199
+
200
+ ### Population attestation
201
+
202
+ #### `client.attest_population(...) → dict`
203
+
204
+ Post a sealed aggregate performance attestation for a model version over a
205
+ defined time window. Required for GMLP, EU AI Act post-market surveillance,
206
+ ISO 42001, and FDA PCCP total-lifecycle monitoring.
207
+
208
+ ```python
209
+ attestation = client.attest_population(
210
+ model_version="ct-lung-v4.1.0",
211
+ period_start="2026-01-01T00:00:00Z",
212
+ period_end="2026-03-31T23:59:59Z",
213
+ site_identifier_hash=sha256_hex(b"site:hospital-a"),
214
+ decision_count=1000,
215
+ confirmed_count=400,
216
+ true_positive_count=180,
217
+ false_positive_count=20,
218
+ true_negative_count=190,
219
+ false_negative_count=10,
220
+ drift_flag=False,
221
+ regulatory_frameworks=["fda_pccp", "gmlp_principle_7"],
222
+ )
223
+ print(attestation["attestation_id"])
224
+ print(attestation["sensitivity"]) # 0.947
225
+ print(attestation["specificity"]) # 0.905
226
+ ```
227
+
228
+ ---
229
+
230
+ ### Offline audit export
231
+
232
+ #### `client.export_packet(decision_id) → dict`
233
+
234
+ Fetch the signed, self-verifying offline audit packet for a decision — a
235
+ single JSON bundle containing the decision envelope, replay record, rationale,
236
+ consequence chain, population context, and Merkle checkpoint. Every nested
237
+ record carries its own signature; the `export_signature` seals the entire
238
+ bundle. A regulator can verify it offline without Thessor infrastructure.
239
+
240
+ ```python
241
+ packet = client.export_packet(envelope_id)
242
+ # packet["export_signature"] seals every field
243
+ ```
244
+
245
+ #### `client.export_to_file(decision_id, filepath) → dict`
246
+
247
+ Fetch the audit packet and write it to `filepath` as pretty-printed JSON.
248
+ Returns the packet dict as well.
249
+
250
+ ```python
251
+ packet = client.export_to_file(envelope_id, "/tmp/audit-dec-001.json")
252
+ ```
253
+
254
+ ---
255
+
256
+ ### MCP middleware
257
+
258
+ For agent frameworks using the Model Context Protocol, use
259
+ `ThessorMCPMiddleware` to seal every tool call automatically:
260
+
261
+ ```python
262
+ from thessor import ThessorMCPMiddleware, sha256_hex
263
+
264
+ middleware = ThessorMCPMiddleware(
265
+ thessor_client=client,
266
+ model_id="claude-sonnet-4-6",
267
+ model_version="20250620",
268
+ agent_id_hash=sha256_hex(b"agent:my-workflow-v1"),
269
+ )
270
+
271
+ # Seal a tool call manually:
272
+ envelope = middleware.seal_tool_call(
273
+ "lookup_icd10",
274
+ tool_input={"description": "type 2 diabetes"},
275
+ tool_output={"code": "E11", "description": "Type 2 diabetes mellitus"},
276
+ confidence=0.99,
277
+ parent_decision_id=prior_envelope_id,
278
+ )
279
+
280
+ # Or use the decorator to seal every call automatically:
281
+ @middleware.seal_mcp_tool
282
+ def search_formulary(tool_input: dict) -> dict:
283
+ return {"tier": 2, "covered": True}
284
+
285
+ result = search_formulary({"drug_name": "metformin"})
286
+ # → tool runs, output sealed, result returned unchanged
287
+ ```
288
+
289
+ `seal_tool_call_async()` is also available for async contexts.
290
+
291
+ ---
292
+
293
+ ## Error handling
294
+
295
+ All API errors raise a subclass of `ThessorError`:
296
+
297
+ ```python
298
+ from thessor import AuthError, ValidationError, RateLimitError, NotFoundError
299
+
300
+ try:
301
+ client.seal(...)
302
+ except ValidationError as exc:
303
+ print(exc.field_errors) # structured per-field validation errors
304
+ except AuthError:
305
+ print("check your API key")
306
+ except RateLimitError:
307
+ print("back off and retry later")
308
+ except NotFoundError:
309
+ print("envelope not found")
310
+ ```
311
+
312
+ 429 and 5xx responses are retried automatically with exponential backoff
313
+ (0.5 s, 1 s, 2 s). 4xx errors are raised immediately.
314
+
315
+ ---
316
+
317
+ ## Changelog
318
+
319
+ ### 0.2.0
320
+ - `Client.seal_with_rationale()` — seal + rationale in one call
321
+ - `Client.seal_agent_decision()` — agent sealing without MCP setup
322
+ - `Client.attest_population()` — population performance attestation
323
+ - `ThessorMCPMiddleware` — MCP-native agent decision sealing
324
+ - Model classes (`SealResult`, `SealWithRationaleResult`, `VerifyResult`,
325
+ `ChainResult`) now exported from the top-level package
326
+ - Version bump to 0.2.0
327
+
328
+ ### 0.1.0
329
+ - Initial release: `Client`, `FHIRConnector`, `DICOMSRConnector`,
330
+ `ATNAConnector`, `HL7Connector`, PHI-safe hashing utilities,
331
+ `export_packet`, `export_to_file`
332
+
333
+ ---
334
+
335
+ ## Releasing
336
+
337
+ To publish a new version to PyPI:
338
+
339
+ 1. Bump version in `sdk/pyproject.toml`, `sdk/thessor/__init__.py`, and `sdk/VERSION`
340
+ 2. `git add sdk/`
341
+ 3. `git commit -m "release: SDK v0.x.x"`
342
+ 4. `git tag sdk-v0.x.x`
343
+ 5. `git push origin main --tags`
344
+
345
+ GitHub Actions will build and push to PyPI automatically.
thessor-0.2.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0