Metadata-Version: 2.4
Name: signatrust
Version: 1.3.1
Summary: The Trust Layer for AI Agents. Emit a verifiable, tamper-evident receipt for every AI decision.
Author-email: Signatrust <hello@signatrust.net>
License: Apache-2.0
Project-URL: Homepage, https://signatrust.net
Project-URL: Documentation, https://signatrust.net/docs
Project-URL: Repository, https://github.com/abokenan444/Signatrust
Project-URL: Issues, https://github.com/abokenan444/Signatrust/issues
Keywords: ai,audit,receipts,ed25519,cryptography,compliance,eu-ai-act,agents
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Signatrust — Python SDK

A verifiable, tamper‑evident receipt for every AI decision. Standard library only — no dependencies.

```python
from signatrust import Signatrust

client = Signatrust(api_key=os.environ["SIGNATRUST_API_KEY"])

result = client.sign(
    model={"provider": "openai", "name": "gpt-4o", "version": "2026.4"},
    decision={
        "type": "loan_rejection",
        "input": user_input,      # hashed locally — raw data never leaves
        "output": agent_output,   # hashed locally
        "risk_level": "high",
        "human_review": True,
        "policies": ["eu-ai-act-high-risk"],
    },
)

receipt = result["receipt"]
assert client.verify(receipt["id"])["valid"]
```

## Zero-Data-Access by default

Signatrust is designed so that the raw content of a decision, the subject's identity, and the reviewer's identity **stay inside your environment**. The SDK only ships cryptographic commitments (SHA-256) to our servers.

| Field | Sent to Signatrust | Stays local |
| --- | --- | --- |
| `input` / `output` raw text | ✗ | ✓ (via `hash_locally=True`, default) |
| `input_hash` / `output_hash` | ✓ | ✓ |
| Model id, risk level, policies, business event | ✓ | ✓ |
| Subject identity (patient MRN, applicant id, claim id) | ✗ (never — you don't send it) | ✓ |
| Reviewer identity, role, specialty | ✗ (never — see [Human review](#human-review-attestation--who-reviewed-and-what-is-their-specialty)) | ✓ |
| `human_review_attestation_hash` (SHA-256 of reviewer info) | ✓ | ✓ |
| Receipt id, receipt hash, signature | ✓ | ✓ (returned to you) |

## Constructor

```python
Signatrust(api_key, base_url="https://signatrust.net", hash_locally=True, timeout=15.0)
```

## Methods

- `sign(decision, model=None, metadata=None, trace=None, scope_declaration=None, human_reviewer=None)` → seal a decision.
- `issue_receipt(...)` → alias for `sign` (identical behaviour).
- `verify(receipt_id)` → verify a stored receipt.
- `verify_receipt(receipt)` → verify a receipt object you hold.
- `get_receipt(receipt_id)` → fetch a sealed receipt.
- `trust_score(agent_id)` → fetch an agent's Trust Score.
- `trace(trace_id=None, mode="every_tool_call")` → open a trace scope for tool-call-level sealing.

Utilities also exported: `fingerprint(content)`, `new_trace_id()`, `human_reviewer_attestation(reviewer)`, and the `ReceiptReferenceStore` class.

## Human review attestation — WHO reviewed and what is their specialty

Signatrust records **whether** a human reviewed a decision (`human_review: True|False`) and optionally a **cryptographic commitment** to the reviewer's identity and specialty (`human_review_attestation_hash`). The reviewer's actual name, role, and specialty **stay on your side** — they never enter our systems.

```python
result = client.sign(
    decision={
        "type": "triage_recommendation",
        "input":  raw_prompt,   # hashed locally
        "output": raw_output,   # hashed locally
        "risk_level": "high",
        "policies": ["triage-protocol-v4", "stemi-fast-track"],
    },
    human_reviewer={
        "id":          "STAFF-7734",              # your internal staff/licence id
        "role":        "attending_physician",     # free-form role label
        "specialty":   "cardiology",              # discipline
        "reviewed_at": "2027-06-01T09:20:00Z",    # ISO-8601, defaults to now
        "note":        "STEMI protocol confirmed",
    },
)

# result["receipt"]["decision"]["human_review"] == True
# result["receipt"]["decision"]["human_review_attestation_hash"] == "sha256:<64 hex>"
# result["human_reviewer"] echoes the reviewer fields for your local storage.
```

### What each side holds

```
Customer supplies to the SDK:                   Sent to Signatrust (enters signed receipt):
──────────────────────────────                  ──────────────────────────────
{                                               {
  "id": "STAFF-7734",                             "human_review": true,
  "role": "attending_physician",                  "human_review_attestation_hash":
  "specialty": "cardiology",                        "sha256:<64 hex>"
  "reviewed_at": "2027-06-01T09:20:00Z",        }
  "note": "STEMI protocol confirmed"            (no reviewer name / role /
}                                                specialty / note travels)
```

Years later, given the reviewer's original details (held by you or your customer's record system), anyone can recompute the same commitment via `human_reviewer_attestation({...})["hash"]` and match it against the historical value inside the sealed receipt — proving **which** reviewer signed off, without Signatrust ever having held that information.

## ReceiptReferenceStore — linking receipts to your own records

Signatrust intentionally does **not** hold a `subject → receipt_id` map. The subject (patient, applicant, employee, claim) belongs to your domain; the receipt belongs to ours. The bridge lives on your side.

The SDK ships a minimal, dependency-free `ReceiptReferenceStore` that persists these bridges as append-only JSONL with mode `0o600` — a reference implementation you can adopt, adapt, or replace with your own database.

```python
from signatrust import Signatrust, ReceiptReferenceStore

client = Signatrust(api_key=os.environ["SIGNATRUST_API_KEY"])
store  = ReceiptReferenceStore(file_path="/var/lib/hospital/signatrust-refs.jsonl")

result = client.sign(
    decision={...},
    human_reviewer={
        "id": "STAFF-7734", "role": "attending_physician", "specialty": "cardiology",
    },
)

# Attach the Receipt ID to your own internal record. Signatrust never sees this row.
store.attach(
    result,
    subject_ref="PT-84721",           # your internal patient/customer id
    case_ref="E-2027-551",            # your internal encounter/case id
    label="triage_recommendation",
    actor="TriageBot v4",
    action="STEMI fast-track routing",
    method_note="protocol=stemi-fast-track; prompt_id=triage-2027-Q3",
)

# Five years later, retrieve by any local identifier:
by_patient = store.find_by_subject("PT-84721")
by_case    = store.find_by_case("E-2027-551")
by_receipt = store.find_by_receipt_id("STR-A1B2C3...")
```

### What each row contains (all local, never sent to Signatrust)

| Field | Purpose |
| --- | --- |
| `receipt_id`, `receipt_hash` | The bridge to the Signatrust receipt. |
| `subject_ref` | Your internal subject id (patient MRN, applicant id, claim id, employee id). |
| `case_ref` | Optional secondary id (encounter, transaction, work-order). |
| `label`, `actor`, `action` | Human-searchable descriptors in *your* vocabulary. |
| `method_note` | Free-text: protocol id, prompt id, chain-of-custody id. |
| `input_hash`, `output_hash` | Copies of the receipt commitments (convenience). |
| `human_reviewer` | Full reviewer object — id, role, specialty, reviewed_at, note. |
| `human_review_attestation_hash` | Copy of the commitment that entered the signed receipt. |
| `tags` | Any non-sensitive tags for filtering. |
| `recorded_at` | When your system wrote the row. |

Deeper walkthrough: <https://signatrust.net/receipt-reference>.

## Chain of custody — tool-call level sealing

By default, ``sign()`` seals the **final decision** only. For agentic workflows
where intermediate tool calls influence the outcome, open a ``trace`` and seal
every step — the full chain (search → db lookup → LLM decision → final response)
becomes a verifiable, tamper-evident timeline sharing one ``trace_id``.

```python
trace = client.trace(mode="every_tool_call")

trace.step(
    step_type="tool_call",
    tool_name="search",
    decision={"type": "tool_result", "output": raw_search_json},
)

trace.step(
    step_type="tool_call",
    tool_name="db_lookup",
    decision={"type": "tool_result", "output": rows_json},
)

trace.step(
    step_type="final_response",
    decision={
        "type": "loan_decision",
        "input": user_application,
        "output": final_decision_json,
        "risk_level": "high",
    },
)

print(trace.trace_id, len(trace.receipts))  # 3
```

### Receipt Mode

| Mode | Seals |
| --- | --- |
| ``final_decision_only`` *(default of ``sign()``)* | Only the final agent output. |
| ``every_tool_call`` | One receipt per tool invocation. |
| ``every_agent_step`` | One receipt per agent reasoning step. |
| ``custom`` | You pick ``step_type`` per call. |

## Decision Boundary Disclosure (DBD)

Declare what your system evaluated and what it excluded — backed by versioned sector schemas. Silent omission is structurally impossible: every domain in the schema must be classified.

```python
result = client.sign(
    decision={"type": "collision_assessment", "input": raw_in, "output": raw_out, "risk_level": "high"},
    scope_declaration={
        "sector_schema_id": "automotive_collision.v1",
        "domains_evaluated": [
            {"domain_id": "physics_impact_force", "status": "computed"},
            {"domain_id": "vehicle_structural_integrity", "status": "computed"},
        ],
        "domains_excluded": [
            {"domain_id": "occupant_biological_impact",
             "reason": "no_data_source_available", "excluded_by": "data_gap"},
            {"domain_id": "pedestrian_third_party_impact",
             "reason": "not_in_scope_of_this_module", "excluded_by": "design"},
            # ...all remaining domains
        ],
    },
)
```

DBD requires that every domain defined by the referenced sector schema appears in either `domains_evaluated` or `domains_excluded` — silent omission is rejected by the server.

## Upgrading from earlier versions

Every field introduced in the SDK is **additive and optional**. Upgrading does not change the shape of receipts you have already sealed and it does not change any existing method signature.

- `human_reviewer`, `human_reviewer_attestation`, `ReceiptReferenceStore` — new, opt-in.
- `decision["human_review_attestation_hash"]` — optional field on the receipt body. `None` / missing values are dropped by canonicalisation, so a receipt sealed **before** this SDK version produces the exact same canonical bytes today as it did then. All historical receipts continue to verify unchanged.
- `trace` and DBD (`scope_declaration`) remain unchanged; existing workflows keep working.

If a call site does not pass `human_reviewer` or `scope_declaration`, the SDK behaves exactly as in previous versions.

## Missing something? Ask us to build it

If your stack, framework, or workflow needs an integration that isn't shipped yet — a specific SDK, a hosted connector, a native adapter for your platform, or a custom field mapping — send the details to **[partners@signatrust.net](mailto:partners@signatrust.net)**. We'll ship what you need.

Please include:

- The runtime or framework (Node/Python/Go/Rust/Java/.NET, LangChain, LlamaIndex, CrewAI, n8n, Zapier, Make, …).
- The subject records you'd want to bind receipts to (patient, applicant, claim, transaction, work-order, …).
- Any human-review workflow (who signs off, what specialty, whether attestation must be verifiable later).
- Whether you need on-prem, hybrid, or self-hosted verification tooling.

## License

Apache-2.0
