Metadata-Version: 2.4
Name: thessor
Version: 0.2.0
Summary: The Thessor SDK — cryptographic AI decision accountability
Project-URL: Homepage, https://thessor.com
Project-URL: Repository, https://github.com/SOLOMONTHESSOR/thessor-api
Author-email: "Thessor, Inc." <solomon@thessor.com>
License: Proprietary
Requires-Python: >=3.9
Requires-Dist: cryptography>=41.0.0
Requires-Dist: httpx>=0.24.0
Description-Content-Type: text/markdown

# Thessor Python SDK

Cryptographic accountability for AI decisions. Seals decisions into
tamper-evident, Ed25519-signed envelopes and verifies them — without ever
requiring you to send PHI to Thessor.

**v0.2.0** · Python ≥ 3.11 · zero third-party dependencies

## Install

```
pip install thessor
```

## Quick start

```python
from thessor import Client

client = Client(api_key="thsr_...")

result = client.seal(
    decision_id="dec-001",
    model_id="cnds-classifier-v3",
    model_version="3.2.1",
    timestamp="2026-06-19T10:00:00Z",
    input_hash="sha256:...",
    output={"label": "approved"},
    confidence_score=0.97,
    policy_version="policy-2026-Q2",
    payload={"finding": "..."},
)
print(result.envelope_id, result.verify_url)

verification = client.verify(result.envelope_id)
assert verification.valid
```

## PHI never has to leave your environment

Use `hash_patient_id()` to turn a patient ID into a one-way SHA-256 hash
before it ever leaves your systems, and pass the hash as `patient_id_hash`
in `attestations`:

```python
from thessor import hash_patient_id

result = client.seal(
    ...,
    attestations={"patient_id_hash": hash_patient_id(patient.mrn)},
)
```

Thessor stores and seals only the hash. `canonicalize()` and `sha256_hex()`
are also exposed for fully offline, trustless verification with no API call
(`client.verify_direct` supports this flow).

---

## API reference

### Sealing decisions

#### `client.seal(...) → SealResult`

Seal a decision into a tamper-evident, Ed25519-signed envelope.

```python
result = client.seal(
    decision_id="dec-001",         # caller-supplied stable ID
    model_id="cnds-classifier-v3",
    model_version="3.2.1",
    timestamp="2026-06-19T10:00:00Z",
    input_hash="sha256:abc123",    # SHA-256 of the canonical input
    output={"label": "approved"},
    confidence_score=0.97,
    policy_version="policy-2026-Q2",
    payload={"finding": "..."},    # extra context (not sealed, not sent)
    attestations={                 # optional: sealed regulatory metadata
        "regulatory_frameworks": ["fda_pccp", "eu_ai_act_high_risk"],
        "clinician_npi": "1234567890",
        "patient_id_hash": hash_patient_id(patient.mrn),
    },
    sync=True,   # default: block until envelope is written
)
# result.envelope_id, result.canonical_hash, result.signature, result.verify_url
```

`sync=False` queues the seal and returns immediately (status `"pending"`).
Poll with `get_seal_status(seal_id)` or use `seal_and_wait()`.

#### `client.seal_with_rationale(...) → SealWithRationaleResult`

Seal a decision and immediately seal its rationale record in one call.

```python
result = client.seal_with_rationale(
    # all seal() fields:
    decision_id="dec-002",
    model_id="cnds-classifier-v3",
    model_version="3.2.1",
    timestamp="2026-06-19T10:00:00Z",
    input_hash="sha256:abc123",
    output={"label": "approved"},
    confidence_score=0.97,
    policy_version="policy-2026-Q2",
    payload={},
    # rationale fields:
    confidence_distribution={"approved": 0.97, "denied": 0.03},
    salient_inputs={"age": 0.42, "risk_score": 0.81},
    policy_logic_applied={"rule": "threshold_0.85", "version": "v3"},
    human_oversight_action="clinician_confirmed",
    human_operator_id_hash=sha256_hex(b"npi:1234567890"),
)
print(result.envelope_id)       # shortcut to result.seal.envelope_id
print(result.rationale_id)
print(result.rationale_hash)
```

#### `client.seal_agent_decision(...) → dict`

Seal an agent tool call without setting up `ThessorMCPMiddleware` directly.
Works with any agent framework (LangChain, AutoGen, custom orchestrators).
Raw inputs and outputs are never sent — only their SHA-256 hashes.

```python
envelope = client.seal_agent_decision(
    tool_name="search_formulary",
    tool_input={"drug_name": "metformin", "plan_id": "BCBS-HMO"},
    tool_output={"formulary_match": True, "tier": 2},
    model_id="gpt-4o",
    model_version="2024-11-20",
    agent_id_hash=sha256_hex(b"agent:workflow-v1"),
    confidence=0.95,
    policy_version="formulary-policy-v2",
    parent_decision_id=prior_envelope_id,   # chains to parent decision
)
print(envelope["envelope_id"])
```

#### `client.seal_and_wait(...) → SealResult`

Queue an async seal and block until it completes or times out.

```python
result = client.seal_and_wait(
    poll_interval=0.5,
    timeout=30.0,
    decision_id="dec-003",
    ...   # same kwargs as seal()
)
```

#### `client.get_seal_status(seal_id) → SealResult`

Poll the status of an async seal (`seal(sync=False)`).

---

### Verification

#### `client.verify(envelope_id) → VerifyResult`

Fetch verification metadata for a sealed envelope.

```python
v = client.verify(result.envelope_id)
print(v.valid, v.canonical_hash, v.regulatory_context)
```

#### `client.verify_with_payload(envelope_id, payload) → VerifyResult`

Verify that a payload matches the sealed envelope's hash and signature.

#### `client.verify_direct(payload, signature_hex, public_key_hex) → VerifyResult`

Fully trustless, offline verification — no envelope lookup, no DB. Useful
for auditors who only have the envelope contents and the published Thessor
public key.

---

### Outcomes and consequence chains

#### `client.seal_outcome(outcome_id, model_id, outcome_type, payload) → SealResult`

Seal an outcome envelope to later link to a decision.

#### `client.link(decision_envelope_id, outcome_envelope_id, causal_claim) → dict`

Causally link a decision envelope to an outcome envelope.

#### `client.get_chain(decision_envelope_id) → ChainResult`

Fetch the full consequence chain (decision + linked outcomes) for a
decision envelope.

---

### Population attestation

#### `client.attest_population(...) → dict`

Post a sealed aggregate performance attestation for a model version over a
defined time window. Required for GMLP, EU AI Act post-market surveillance,
ISO 42001, and FDA PCCP total-lifecycle monitoring.

```python
attestation = client.attest_population(
    model_version="ct-lung-v4.1.0",
    period_start="2026-01-01T00:00:00Z",
    period_end="2026-03-31T23:59:59Z",
    site_identifier_hash=sha256_hex(b"site:hospital-a"),
    decision_count=1000,
    confirmed_count=400,
    true_positive_count=180,
    false_positive_count=20,
    true_negative_count=190,
    false_negative_count=10,
    drift_flag=False,
    regulatory_frameworks=["fda_pccp", "gmlp_principle_7"],
)
print(attestation["attestation_id"])
print(attestation["sensitivity"])    # 0.947
print(attestation["specificity"])    # 0.905
```

---

### Offline audit export

#### `client.export_packet(decision_id) → dict`

Fetch the signed, self-verifying offline audit packet for a decision — a
single JSON bundle containing the decision envelope, replay record, rationale,
consequence chain, population context, and Merkle checkpoint. Every nested
record carries its own signature; the `export_signature` seals the entire
bundle. A regulator can verify it offline without Thessor infrastructure.

```python
packet = client.export_packet(envelope_id)
# packet["export_signature"] seals every field
```

#### `client.export_to_file(decision_id, filepath) → dict`

Fetch the audit packet and write it to `filepath` as pretty-printed JSON.
Returns the packet dict as well.

```python
packet = client.export_to_file(envelope_id, "/tmp/audit-dec-001.json")
```

---

### MCP middleware

For agent frameworks using the Model Context Protocol, use
`ThessorMCPMiddleware` to seal every tool call automatically:

```python
from thessor import ThessorMCPMiddleware, sha256_hex

middleware = ThessorMCPMiddleware(
    thessor_client=client,
    model_id="claude-sonnet-4-6",
    model_version="20250620",
    agent_id_hash=sha256_hex(b"agent:my-workflow-v1"),
)

# Seal a tool call manually:
envelope = middleware.seal_tool_call(
    "lookup_icd10",
    tool_input={"description": "type 2 diabetes"},
    tool_output={"code": "E11", "description": "Type 2 diabetes mellitus"},
    confidence=0.99,
    parent_decision_id=prior_envelope_id,
)

# Or use the decorator to seal every call automatically:
@middleware.seal_mcp_tool
def search_formulary(tool_input: dict) -> dict:
    return {"tier": 2, "covered": True}

result = search_formulary({"drug_name": "metformin"})
# → tool runs, output sealed, result returned unchanged
```

`seal_tool_call_async()` is also available for async contexts.

---

## Error handling

All API errors raise a subclass of `ThessorError`:

```python
from thessor import AuthError, ValidationError, RateLimitError, NotFoundError

try:
    client.seal(...)
except ValidationError as exc:
    print(exc.field_errors)   # structured per-field validation errors
except AuthError:
    print("check your API key")
except RateLimitError:
    print("back off and retry later")
except NotFoundError:
    print("envelope not found")
```

429 and 5xx responses are retried automatically with exponential backoff
(0.5 s, 1 s, 2 s). 4xx errors are raised immediately.

---

## Changelog

### 0.2.0
- `Client.seal_with_rationale()` — seal + rationale in one call
- `Client.seal_agent_decision()` — agent sealing without MCP setup
- `Client.attest_population()` — population performance attestation
- `ThessorMCPMiddleware` — MCP-native agent decision sealing
- Model classes (`SealResult`, `SealWithRationaleResult`, `VerifyResult`,
  `ChainResult`) now exported from the top-level package
- Version bump to 0.2.0

### 0.1.0
- Initial release: `Client`, `FHIRConnector`, `DICOMSRConnector`,
  `ATNAConnector`, `HL7Connector`, PHI-safe hashing utilities,
  `export_packet`, `export_to_file`

---

## Releasing

To publish a new version to PyPI:

1. Bump version in `sdk/pyproject.toml`, `sdk/thessor/__init__.py`, and `sdk/VERSION`
2. `git add sdk/`
3. `git commit -m "release: SDK v0.x.x"`
4. `git tag sdk-v0.x.x`
5. `git push origin main --tags`

GitHub Actions will build and push to PyPI automatically.
