# Quickstart: MailioReceipt v1

**Date**: 2026-02-04
**Branch**: `001-mailio-receipt-v1`

## Installation

```bash
pip install mailio-receipt
```

Or with development dependencies:

```bash
pip install mailio-receipt[dev]
```

---

## Basic Usage

### 1. Create a Receipt for a Planned Action

```python
from datetime import datetime, timezone
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend

from mailio_receipt import (
    create_receipt,
    create_event,
    Actors,
    Intent,
    IntentKind,
)

# Generate a signing key (in practice, load from secure storage)
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())

# Define the actors
actors = Actors(
    issuer="did:web:agent.example.com",
    subject="did:web:user.example.com",
)

# Define what the agent plans to do
intent = Intent(
    kind=IntentKind.ACTION_PLANNED,
    action="invoice.send",
    category="financial",
    scope={
        "invoiceId": "INV-2026-001",
        "amount": 1500.00,
        "currency": "USD",
        "recipient": "client@example.com",
    },
)

# Create the event
event = create_event(
    flow_id="660f9400-f39c-51e5-b827-557766551111",
    actors=actors,
    intent=intent,
)

# Sign and create the receipt
receipt = create_receipt(
    event=event,
    private_key=private_key,
    signer_did="did:web:agent.example.com",
    role="agent",
)

print(f"Receipt created: {receipt.event_digest[:16]}...")
print(f"Event ID: {receipt.event.id}")
```

---

### 2. Request and Grant Approval

```python
from mailio_receipt import chain_from

# Agent requests approval (chained to the planned action)
request_event = create_event(
    flow_id=receipt.event.flow_id,
    actors=actors,
    intent=Intent(
        kind=IntentKind.APPROVAL_REQUESTED,
        action="invoice.send",
        category="financial",
        scope={"approvalFor": receipt.event_digest},
    ),
    chain=chain_from(receipt, reason="Requesting user approval before sending invoice"),
)

request_receipt = create_receipt(
    event=request_event,
    private_key=private_key,
    signer_did="did:web:agent.example.com",
)

# User grants approval
user_private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())

approval_event = create_event(
    flow_id=receipt.event.flow_id,
    actors=Actors(
        issuer="did:web:user.example.com",
        subject="did:web:user.example.com",
    ),
    intent=Intent(
        kind=IntentKind.APPROVAL_GRANTED,
        action="invoice.send",
        category="financial",
        scope={"approvedReceiptDigest": request_receipt.event_digest},
    ),
    chain=chain_from(request_receipt),
    ttl="PT1H",  # Valid for 1 hour
)
approval_receipt = create_receipt(
    event=approval_event,
    private_key=user_private_key,
    signer_did="did:web:user.example.com",
    role="user",
)

print(f"Approval granted: {approval_receipt.event_digest[:16]}...")
```

---

### 3. Verify a Receipt

```python
import asyncio
from mailio_receipt import verify_receipt, DIDWebResolver

async def verify():
    resolver = DIDWebResolver(cache_ttl=300)

    result = await verify_receipt(receipt, resolver=resolver)

    if result.valid:
        print("✓ Receipt verified")
        for signer in result.signers:
            print(f"  Signed by: {signer.did} ({signer.role})")
    else:
        print(f"✗ Verification failed: {result.error}")

asyncio.run(verify())
```

---

### 4. Check Execution Eligibility

```python
from mailio_receipt import check_execution

# Collect all receipts in the flow
receipts = [receipt, request_receipt, approval_receipt]

# Check if execution is allowed
decision = check_execution(
    receipts=receipts,
    action_digest=receipt.event_digest,
    decision_time=datetime.now(timezone.utc),
)

if decision.allowed:
    print(f"✓ Execution allowed (approval: {decision.approval_digest[:16]}...)")
else:
    print(f"✗ Execution blocked: {decision.reason}")
    if decision.blocking_reason:
        print(f"  Reason: {decision.blocking_reason.name}")
```

---

### 5. Revoke an Approval

```python
from mailio_receipt import chain_from

# User revokes the approval
revocation_event = create_event(
    flow_id=receipt.event.flow_id,
    actors=Actors(
        issuer="did:web:user.example.com",
        subject="did:web:user.example.com",
    ),
    intent=Intent(
        kind=IntentKind.APPROVAL_REVOKED,
        action="invoice.send",
        category="financial",
        scope={"revokedApprovalDigest": approval_receipt.event_digest},
    ),
    chain=chain_from(approval_receipt),
)
revocation_receipt = create_receipt(
    event=revocation_event,
    private_key=user_private_key,
    signer_did="did:web:user.example.com",
    role="user",
)

# Check execution again (should be blocked)
receipts.append(revocation_receipt)

decision = check_execution(
    receipts=receipts,
    action_digest=receipt.event_digest,
    decision_time=datetime.now(timezone.utc),
)

print(f"Execution allowed: {decision.allowed}")  # False
print(f"Blocked by: {decision.revocation_digest[:16]}...")
```

---

### 6. Bind Artifacts

```python
import hashlib
from mailio_receipt import Binding, ArtifactBinding

# Hash the artifact
invoice_pdf = b"... invoice content ..."
artifact_digest = hashlib.sha256(invoice_pdf).hexdigest()

# Create event with binding
event_with_binding = create_event(
    flow_id="...",
    actors=actors,
    intent=intent,
    binding=Binding(
        artifacts=(
            ArtifactBinding(
                name="invoice.pdf",
                digest=artifact_digest,
                media_type="application/pdf",
            ),
        ),
    ),
)
```

---

## Serialization

```python
# To JSON
json_str = receipt.to_json()
print(json_str)

# From JSON
loaded_receipt = MailioReceipt.from_json(json_str)
assert loaded_receipt.digest == receipt.digest

# To dict (for storage)
data = receipt.to_dict()
```

---

## Error Handling

```python
from mailio_receipt import (
    MailioReceiptError,
    SignatureError,
    DIDResolutionError,
    ExecutionBlockedError,
    RevocationError,
)

try:
    result = await verify_receipt(receipt, resolver=resolver)
except DIDResolutionError as e:
    if isinstance(e, DIDNetworkError):
        print(f"Network error (retryable): {e}")
    else:
        print(f"DID not found: {e}")
except SignatureError as e:
    print(f"Invalid signature: {e}")
except MailioReceiptError as e:
    print(f"Receipt error: {e}")
```

---

## Testing with Mock DID Resolver

```python
from unittest.mock import AsyncMock
from cryptography.hazmat.primitives import serialization

class MockDIDResolver:
    def __init__(self, keys: dict[str, ec.EllipticCurvePublicKey]):
        self.keys = keys

    async def resolve(self, did: str):
        return {"id": did, "verificationMethod": [...]}

    async def get_verification_key(self, did: str, key_id: str = None):
        return self.keys.get(did)

# In tests
resolver = MockDIDResolver({
    "did:web:agent.example.com": private_key.public_key(),
})
```

---

## Next Steps

1. **Production DID Setup**: Configure your DID:web endpoint with your public keys
2. **Key Management**: Use a secure key storage solution (HSM, vault, etc.)
3. **Event Bus Integration**: Announce receipts via your preferred message broker
4. **Chain Storage**: Implement receipt persistence in your application
