Metadata-Version: 2.4
Name: agentvisa-verifier
Version: 0.3.0
Summary: Platform-side verification library for AgentVisa v3 authentication
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.25
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: fastapi>=0.110; extra == "dev"
Requires-Dist: flask>=3.0; extra == "dev"

# agentvisa-verifier

Platform-side verification for AgentVisa v3 authentication - accept requests
from a real, liveness-verified human's delegated agent without learning who
that human is, and without running any cryptography yourself.

**Hosted mode is the default:** one middleware line forwards the agent's
credential to the AgentVisa verify endpoint, which runs the real
zero-knowledge checks and returns the agent's pseudonym. No Node.js, no proof
artifacts, no Merkle roots on your side. The offline library path (real Mina
proof verification through the `agentvisa-core` Node bridge) is still
available as the escape hatch.

**See [INTEGRATION.md](INTEGRATION.md) for the full 5-minute integration
guide** - env vars, middleware snippet, error codes, and production setup. A
runnable copy-paste version ships in `examples/demo-platform/`.

## Install

```bash
pip install agentvisa-verifier
```

Python 3.10+ is the only requirement for hosted mode.

## Usage (hosted mode, FastAPI)

```python
import os
from fastapi import FastAPI, Request
from agentvisa_verifier import AgentVisaGate

app = FastAPI()
app.add_middleware(
    AgentVisaGate,
    verify_endpoint=os.environ["AGENTVISA_VERIFY_ENDPOINT"],
)

@app.get("/api/me")
def me(request: Request):
    return {"pseudonym": request.state.agentvisa_pseudonym}
```

Flask works the same way: `AgentVisaGate(app, verify_endpoint=...)`, and the
verified pseudonym lands on `request.agentvisa_pseudonym`. Account linking
(the connect lane) is `ConnectLane` + `SqliteConnectStore` - see
INTEGRATION.md section 6.

## Usage (offline library path)

```python
import os
from agentvisa_verifier import bundled_verification_key, verify_agentvisa_auth

result = verify_agentvisa_auth(
    request.headers["X-AgentVisa-Auth"],
    trusted_roots=frozenset(os.environ["AGENTVISA_TRUSTED_ROOTS"].split(",")),
    expected_audience="https://my-platform.example",
    expected_scope_hash=os.environ["AGENTVISA_SCOPE_HASH"],
    verification_key=bundled_verification_key(),
)

if result.ok:
    account = lookup_account(result.pseudonym)
    grant_session(account)
else:
    reject(f"AgentVisa auth failed: {result.error}")
```

## Verification contract

Verification is split into two layers (pinned in `docs/API-CONTRACT.md`):

- `verify_delegation()` - the rare path, runs once per delegation: header
  decode, proof artifact fetch + `proof_sha256` content addressing, real Mina
  proof verification via the Node bridge, Merkle root trust, authorization
  validity window, scope hash match, delegation nullifier replay.
- `verify_session_signature()` - the fast path, runs on every presentation:
  challenge freshness, audience match, challenge nonce replay, Schnorr
  signature verification via the `verify-signature` Node bridge. No artifact
  fetch, no proof machinery, no concurrency cap.

`verify_agentvisa_auth()` is the retained single-call wrapper: it runs the
delegation layer, threads the verified session public key into the session
layer, and returns the delegation's pseudonym on success. Checks are
fail-closed, in order.

## Replay stores

- `MemoryReplayStore` - dev/testing only
- `SqliteReplayStore` - single server, shared across gunicorn workers
- `RedisReplayStore` - multi-server deployments

## Operational notes

- Concurrent verifications are capped (default 4) with a queue timeout;
  excess load fails fast with `verifier_busy` instead of spawning unbounded
  Node processes.
- Successful crypto verifications are cached briefly (default 60s) keyed by
  artifact hash + challenge nonce + signature; policy checks always re-run.
- The Mina verification key ships pinned inside the package
  (`bundled_verification_key()`); it is never fetched at request time.
