Metadata-Version: 2.4
Name: signet4ai
Version: 0.9.1
Summary: Signet — the verifiable-attestation layer for AI. Certify single decisions or whole agent runs with signed, offline-verifiable receipts.
Author: Priza Technologies Inc.
License: Proprietary
Project-URL: Homepage, https://app.signet4ai.com
Project-URL: Documentation, https://app.signet4ai.com/developers
Keywords: ai,compliance,agents,audit,eu-ai-act,langchain,langgraph,verification
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == "langchain"
Provides-Extra: otel
Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
Provides-Extra: verify
Requires-Dist: cryptography>=41; extra == "verify"

# Signet Python SDK

The verifiable-attestation layer for AI. Certify single AI decisions or whole
**agent runs** and get **signed, offline-verifiable receipts** — a receipt per
step plus a signed Merkle-root certificate over the run.

Zero runtime dependencies (stdlib only). The LangChain/LangGraph adapter is optional.

```bash
pip install signet4ai               # core client
pip install "signet4ai[langchain]"  # + LangChain/LangGraph adapter
```

Get an API key from the dashboard: **https://app.signet4ai.com/developers**

```bash
export SIGNET_API_KEY=sk_live_…
```

## Certify one decision

```python
from signet4ai import SignetClient

signet = SignetClient()  # reads SIGNET_API_KEY

r = signet.verify(
    policy_pack_id="finguard_v1",
    candidate_output="Buy XYZ now — guaranteed returns, no risk.",
)
print(r["verdict"])                       # -> "fail"
print(signet.verify_receipt(r["certificate"])["valid"])  # independent check -> True
```

## Certify an agent run

Record steps as your agent runs, then certify. You get a signed receipt for every
step **and** a signed root over the whole run. Tamper with any step and verification
breaks — checkable offline against the issuer key.

```python
from signet4ai import SignetClient

signet = SignetClient()

run = signet.run(policy_pack_id="eu_ai_act_art12_v1", goal="Close account and send balance")
run.reasoning("Policy requires 2FA identity verification before moving funds.")
run.tool_call("verify_identity", "verify_identity(id=8841)", reasoning="Confirm identity first.")
run.tool_result("verify_identity", "verified: true (2FA)")
run.tool_call("get_balance", "get_balance(id=8841)")
run.tool_result("get_balance", "cleared_balance: 12,430.18 GBP")
run.output("Verified you and transferred £12,430.18, then closed the account.")

result = run.certify()
print(result.verdict, result.risk_score)      # -> pass 0.0
print(result.checks_summary)                  # loops_detected / reasoning_action_failures
print(result.verify()["valid"])               # -> True

# tamper-evidence: edit any signed step and re-verify -> valid is False
```

`run` is also a context manager that auto-certifies on clean exit:

```python
with signet.run("eu_ai_act_art12_v1", goal="…") as run:
    run.reasoning("…")
    run.tool_call("db", "SELECT …")
print(run.result.verdict)
```

## LangChain / LangGraph — drop-in

`SignetCertifier` is a callback handler. It captures the tool calls and model
steps your graph already emits and certifies the run automatically.

```python
from signet4ai import SignetClient
from signet4ai.langgraph import SignetCertifier

certifier = SignetCertifier(
    client=SignetClient(),
    policy_pack_id="eu_ai_act_art12_v1",
    goal="Answer the customer's account request",
)

graph.invoke(state, config={"callbacks": [certifier]})

print(certifier.result.verdict)             # pass | warn | fail | abstain
print(certifier.result.verify()["valid"])   # independent offline check
```

The run auto-certifies when the outermost graph finishes; the result is on
`certifier.result`. Pass `auto_certify=False` to call `certifier.certify()` yourself,
or `on_certify=fn` to get a callback with the `RunResult`.

## What gets checked

Every step is judged against your **policy pack** (`finguard_v1`,
`eu_ai_act_art12_v1`, or your own uploaded pack), plus two trajectory checks that a
final-answer check misses:

- **Step repetition / non-progress** (agent loops) — deterministic.
- **Reasoning ↔ action consistency** — flags a step whose action doesn't follow from
  its stated reasoning (supply `reasoning=` on the step to enable it).

## Any framework (CrewAI, OpenAI Agents SDK, LlamaIndex, custom loops)

You don't need a bespoke adapter per framework. Two generic mechanisms cover the field:

**1. Tool-wrapping (works with anything that calls Python tools).** Wrap your tool
callables; every call is auto-recorded. No framework dependency.

```python
run = signet.recorder("eu_ai_act_art12_v1", goal="Handle the request")

@run.tool                       # decorate your tools
def get_balance(customer_id): ...

search = run.wrap(search)       # or wrap existing callables

# ... run your agent (CrewAI, OpenAI Agents SDK, custom loop) using these tools ...
run.output(final_answer)
result = run.certify()
```

**2. OpenTelemetry (covers the whole instrumented ecosystem).** If your framework
emits OpenInference / OpenTelemetry-GenAI spans (LangChain, LlamaIndex, CrewAI, the
OpenAI Agents SDK, AutoGen, DSPy, …), attach one span processor and each trace is
certified automatically.

```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from signet4ai.otel import SignetSpanProcessor       # pip install "signet4ai[otel]"

provider = TracerProvider()
processor = SignetSpanProcessor(policy_pack_id="eu_ai_act_art12_v1")  # or agent_id="ag_…"
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

# ... instrument your framework (e.g. OpenInference) and run your agent ...
print(processor.result.verdict, processor.result.verify()["valid"])
```

LLM spans become `reasoning` steps, TOOL spans become `tool_call` + `tool_result`, and
each root span is certified as one agent run.

## Agents (registered policies)

Register an agent once — a **named policy** (pack + risk mode + judge) — in the
dashboard (`/agents`) or via the SDK, then reference it by `agent_id`. Runs group
per agent and you change the policy in one place. (Signet doesn't run your agent;
you do, and reference this.)

```python
agent = signet.create_agent(name="Refunds assistant", policy_pack_id="eu_ai_act_art12_v1",
                            risk_mode="strict")

run = signet.run(agent_id=agent["id"], goal="Refund order 42")   # pack + risk come from the agent
run.reasoning("…"); run.tool_call("refund", "refund(order=42)"); run.output("Refunded.")
result = run.certify()

signet.list_agents()                 # all your agents
signet.agent_runs(agent["id"])       # certified-run history for one agent
```

`SignetCertifier(agent_id="…")` works the same way for LangGraph.

## Verify offline — no trust in Signet

Certificates are self-verifying: recompute the hashes, check each Ed25519 signature against
the key embedded in the receipt, recompute the Merkle root — all locally, with **no call to our
servers**. Pin the issuer's public key once and you never need us again to prove a run is authentic.

```bash
pip install "signet4ai[verify]"     # adds `cryptography` for the signature check
```

```python
from signet4ai.verify import verify_trajectory

issuer = signet.issuer_key(project_id)["public_key"]   # fetch + pin once (public key, safe to store)
report = result.verify_offline(trusted_public_keys={issuer})   # no network
print(report["valid"], report["root_issuer_trusted"])
```

`verify_receipt(...)` does the same for a single receipt. Tamper with any step, drop or reorder
one, or present a receipt signed by an untrusted key, and `valid` is `False`.

## Real-time gating

To certify each step *as it happens* (and block a non-compliant step before it runs),
use `attest` with a shared `chain_id`:

```python
signet.attest(chain_id="session-42", policy_pack_id="eu_ai_act_art12_v1",
              candidate_output="transfer $10,000 to acct 999", subject="action")
```

## Verify a workflow trace

Deterministically certify a whole execution trace against workflow obligations
(precedence, threshold, approval-count, role, exclusion) with the So2
constraint-feasibility engine — no model or LLM involved. You get a signed,
offline-verifiable receipt plus per-obligation capacity-obstruction certificates.

```python
from signet4ai import SignetClient

signet = SignetClient()

trace = [
    {"type": "submit_payment", "seq": 3, "amount": 75000, "role": "ap_clerk"},
    {"type": "approval", "seq": 1, "role": "finance"},
    {"type": "approval", "seq": 2, "role": "compliance"},
]
requirements = [
    {"id": "dual_control", "action": "submit_payment", "threshold_amount": 50000,
     "requires": {"approvals": 2, "approver_roles": ["finance", "compliance"], "must_precede": True}},
]

r = signet.verify_workflow(trace, requirements)
print(r["verdict"])                        # -> "pass"
print(signet.verify_receipt(r["certificate"])["valid"])  # independent check -> True
```

## Course-correct mid-run, before the effect lands

`verify_workflow` tells you a **finished** run broke an obligation — useful for the auditor,
too late for the CFO. `continuation` answers the question that saves the trajectory: given the
steps executed **so far**, does a legal completion still exist?

```python
requirement = {"id": "dual_control", "action": "submit_payment", "threshold_amount": 50000,
               "requires": {"approvals": 2, "approver_roles": ["finance", "compliance"],
                            "must_precede": True}}

# mid-run: finance has signed, the payment has NOT executed yet
r = signet.continuation([{"type": "approval", "role": "finance", "seq": 1}], [requirement])
print(r["corridor_status"])              # -> "AT_RISK"  (a legal completion still exists)
print(r["least_disruptive"]["detail"])   # -> "obtain sign-off from ['compliance'] before submit_payment"

# one step later, after the wire goes out
r = signet.continuation([{"type": "approval", "role": "finance", "seq": 1},
                         {"type": "submit_payment", "amount": 120000, "seq": 2}], [requirement])
print(r["corridor_status"])              # -> "DEAD"  (only compensating routes remain)
```

`VIABLE` / `AT_RISK` / `DEAD` / `INCOMPLETE_SEMANTICS`. Deterministic — no model, no LLM.
Advisory calls are unmetered; you pay when you take a receipt (`sign=True`).

### Exact, independently checkable selection

`continuation_exact` enumerates a **committed finite** candidate space and returns the
least-disruptive admitted continuation, with a certificate a third party re-checks *by
re-enumerating* — so a shrunken space, a forged choice or an understated cost is caught.

```python
e = signet.continuation_exact(
    prefix, [requirement],
    space={"obtainable_approvals": ["compliance"], "reducible_amounts": [40000],
           "allow_stop": True, "max_combination": 2},
    plan={"action": "submit_payment", "amount": 120000})

print(e["status"])                       # -> "CERTIFIED_LEAST_DISRUPTIVE"
print([d["kind"] for d in e["chosen"]["delta"]])   # -> ["obtain_approval"]  (not a value cut)
print(signet.check_continuation(e["continuation_certificate"], prefix, [requirement])["valid"])
```

`CERTIFIED_LEAST_DISRUPTIVE` is returned **only** when the declared space was enumerated
exhaustively; a truncated search downgrades to `BEST_WITHIN_BUDGET` and is never labelled exact.

## Signet 2.0 engine

Deterministic engines for the parts of compliance that are decidable. All of these are
model-free.

```python
# Exact least-disruptive repair over a compiled instance, with metrics MEASURED on it
r = signet.rqs_solve(instance)
print(r["status"], r["cost"], r["metrics"]["riw"], r["metrics"]["qcr"])
print(signet.rqs_check(instance, {"feasible": r["feasible"], "cost": r["cost"],
                                  "assignment": r["assignment"]})["valid"])

# Authority cannot expand through delegation
d = signet.delegation(chain, now_ts=100, root_issuer="root",
                      capabilities=["issue_refund"], amounts={"amount": 800})
print(d["authorization"]["status"])   # -> "MISSING_AUTHORITY"
                                      #    amount=800 exceeds delegated limit 500

# Authorisation is bound to one exact action: a repair may change the amount,
# it may not become a different request
signet.admit_repair(original, {**original, "amount": "90.00"})["admitted"]      # True
signet.admit_repair(original, {**original, "request_id": "other"})["admitted"]  # False

# Nothing is publishable without a sealed, registered basis
signet.seal_suite(scenarios, suite_id="refund-sealed-v1")["merkle_root"]
signet.metric("unsafe_repair_rate", {"unsafe_repairs": 2, "repaired_results": 100})
signet.metric("accuracy", {...})       # raises — not a registered metric
```

Also: `repair(...)` (verified repair search — distinguishes a real infeasibility proof from a
timeout), `mine_traces(...)` (find loops and unsafe shapes in recorded runs),
`constitution(...)` (validate a Trajectory Constitution and compile precedents — examples become
precedents, not scripts), `compile_policy(...)`, `quotient_sufficiency(...)`,
`publish_domain_pack(...)`.

## What we do *not* claim

Two calls exist so you can check our claims rather than take them:

```python
signet.capabilities()          # which engine modules are reachable over HTTP
signet.conformance()           # proof-maturity ledger, including known limits
```

`conformance()` reports **M2** (executable certification: independent checkers, exhaustive
oracle, second encodings, differential + mutation tests, fuzzing) and states plainly that **M3
(machine-checked proofs) and M4 (audit) are not reached**. It also publishes the limits: quotient
sufficiency is verified over a *bounded* space rather than proved for unbounded traces, exactness
is relative to a *committed finite* candidate space, and solver optimizers are used only as
proposers because their reported optima are re-certified.

`confidential_status()` reports whether the confidential-compute rail is real or **simulated**.
While it is mocked, no result can reach a validated-FHE status — by construction, not by policy.

## API surface

| Method | Purpose |
|---|---|
| `verify(...)` | certify one output → verdict + signed receipt |
| `verify_workflow(trace, requirements)` | deterministically certify a full trace against workflow obligations |
| `attest(chain_id, ...)` | certify one step, chained (real-time gating) |
| `run(...)` / `AgentRun` | build + certify a whole agent run |
| `certify_trajectory(steps=...)` | certify a run in one call |
| `verify_trajectory(result)` | offline-verify a whole run |
| `verify_receipt(receipt)` | offline-verify one receipt |
| `policy_packs()` / `models()` | list available packs / judge models |
| `list_keys()` / `create_key()` / `revoke_key()` | manage API keys |
| **Course correction** | |
| `continuation(prefix, requirements)` | mid-run corridor: VIABLE / AT_RISK / DEAD + least-disruptive fix |
| `continuation_exact(prefix, reqs, space, plan)` | exact selection over a committed finite space |
| `check_continuation(cert, prefix, reqs)` | independently re-verify a continuation certificate |
| **2.0 engine** | |
| `rqs_solve(instance)` / `rqs_check(...)` | exact core + independent verification, with measured RIW/QCR |
| `repair(policy, action, tool, pack)` | verified repair search (infeasibility proof ≠ timeout) |
| `delegation(chain, ...)` | resolve delegated authority; it can never expand |
| `execution_token(...)` / `admit_repair(...)` | bind authorisation to one exact action |
| `verify_execution_receipt(receipt)` | offline-verify an execution receipt |
| `mine_traces(traces)` | mine recorded runs for loops and unsafe shapes |
| `constitution(constitution, examples=...)` | validate + compile precedents (precedents, not scripts) |
| `compile_policy(policy, context=...)` | STCF canonicalisation + Kleene three-valued evaluation |
| `quotient_sufficiency(requirement, alphabet)` | σ sufficiency over a declared space + field ablation |
| `publish_domain_pack(pack)` | publication gate (fails closed) |
| `seal_suite(scenarios)` / `metric(id, counts)` | sealed benchmark suites; unregistered metrics refused |
| **Disclosure** | |
| `capabilities()` | which modules are reachable over HTTP |
| `conformance()` | proof-maturity ledger + known limits |
| `confidential_status()` | is the confidential rail real or simulated? |
