# Data Model: MailioReceipt v1

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

## Overview

This document defines the data structures for MailioReceipt v1. All models are implemented
as frozen dataclasses with full type annotations per constitution requirements.

---

## Entity Relationship Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│                        MailioReceipt                            │
│  type: "MailioReceipt"                                          │
│  v: "v1"                                                        │
│  eventDigest: str (SHA-256 hex)                                 │
├─────────────────────────────────────────────────────────────────┤
│  event ────────────────────┐                                    │
│  signatures[] ─────────────┼───┐                                │
└────────────────────────────┼───┼────────────────────────────────┘
                             │   │
                             ▼   │
┌─────────────────────────────────────────────────────────────────┐
│                     MailioAuditEvent                            │
│  type: "MailioAuditEvent"                                       │
│  v: "v1"                                                        │
│  id: str (UUID)                                                 │
│  flowId: str (UUID)                                             │
│  ts: str (RFC3339)                                              │
├─────────────────────────────────────────────────────────────────┤
│  actors ───────────────────┬─────▶ Actors                       │
│  intent ───────────────────┼─────▶ Intent                       │
│  chain? ───────────────────┼─────▶ Chain (optional)             │
│  binding? ─────────────────┼─────▶ Binding (optional)           │
│  ttl? ─────────────────────┤      str (ISO8601 duration)        │
│  meta? ────────────────────┤      dict[str, Any]                │
└────────────────────────────┴────────────────────────────────────┘
                             │
                             ▼
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│     Actors       │  │     Intent       │  │     Chain        │
│  issuer: str     │  │  kind: str       │  │  prevReceiptDi-  │
│  subject: str    │  │  action: str     │  │    gest: str     │
│  authority?: str │  │  category: str   │  │  prevReceiptRef?:│
│  others?: list   │  │  scope: dict     │  │    str           │
└──────────────────┘  └──────────────────┘  │  reason?: str    │
                                            └──────────────────┘
                                  │
┌─────────────────────────────────┼───────────────────────────────┐
│                            Signature                            │
│  role: "user" | "agent" | "authority"                           │
│  signer: str (DID)                                              │
│  alg: "ES256" | "EdDSA"                                         │
│  signed: SignedMetadata                                         │
│  jws: str (detached JWS compact)                                │
└─────────────────────────────────────────────────────────────────┘
```

---

## Entity Definitions

### MailioReceipt

The top-level container for a signed event.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `Literal["MailioReceipt"]` | ✅ | Fixed value |
| `v` | `Literal["v1"]` | ✅ | Version identifier |
| `event` | `MailioAuditEvent` | ✅ | The event payload |
| `eventDigest` | `str` | ✅ | SHA-256 hex digest of RFC8785-canonicalized event |
| `signatures` | `list[Signature]` | ✅ | One or more signatures (min 1) |

**Validation Rules**:
- `eventDigest` MUST equal `sha256(canonicalize(event)).hexdigest()`
- `signatures` MUST contain at least one valid signature
- `type` and `v` are immutable constants

**Uniqueness**: Receipts are uniquely identified by their digest (SHA-256 of the entire
canonicalized receipt structure). Chain references use the event digest, not the full
receipt digest.

---

### MailioAuditEvent

The semantic content of a receipt.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `Literal["MailioAuditEvent"]` | ✅ | Fixed value |
| `v` | `Literal["v1"]` | ✅ | Version identifier |
| `id` | `str` | ✅ | Globally unique event ID (UUID v4) |
| `flowId` | `str` | ✅ | Workflow instance ID (UUID) |
| `ts` | `str` | ✅ | RFC3339 timestamp (UTC) |
| `actors` | `Actors` | ✅ | Participants in this event |
| `intent` | `Intent` | ✅ | What this event represents |
| `chain` | `Chain \| None` | ❌ | Link to previous receipt |
| `binding` | `Binding \| None` | ❌ | Artifact/message bindings |
| `ttl` | `str \| None` | ❌ | ISO8601 duration (e.g., "PT1H") |
| `meta` | `dict[str, Any] \| None` | ❌ | Arbitrary metadata |

**Validation Rules**:
- `id` MUST be a valid UUID string
- `flowId` MUST be a valid UUID string
- `ts` MUST be RFC3339 format with timezone
- `ttl` if present MUST be valid ISO8601 duration

---

### Actors

Identifies participants in an event.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `issuer` | `str` | ✅ | DID of entity asserting this event |
| `subject` | `str` | ✅ | DID of user/principal affected |
| `authority` | `str \| None` | ❌ | DID of governing entity |
| `others` | `list[str] \| None` | ❌ | Additional participant DIDs |

**Validation Rules**:
- All DID strings MUST start with `did:` prefix
- `issuer` and `subject` are always required
- For DID:web, format: `did:web:domain:path:segments`

---

### Intent

Describes what the event represents.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `kind` | `str` | ✅ | Lifecycle event type (see Intent Kinds) |
| `action` | `str` | ✅ | Domain-specific action (e.g., "invoice.send") |
| `category` | `str` | ✅ | High-level classification |
| `scope` | `dict[str, Any]` | ✅ | Structured action data |

**Intent Kinds (v1)**:
```
mailio.workflow.action.planned
mailio.workflow.approval.requested
mailio.workflow.approval.granted
mailio.workflow.approval.revoked
mailio.workflow.action.executed
mailio.workflow.action.failed
mailio.workflow.notify
```

**Validation Rules**:
- `kind` SHOULD be one of the v1 intent kinds (custom kinds allowed)
- `action` follows dot-notation convention: `domain.verb`
- `scope` is opaque to the library; structure defined by domain

---

### Chain

Links this event to its predecessor.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `prevReceiptDigest` | `str` | ✅ | SHA-256 hex digest of previous event (eventDigest) |
| `prevReceiptRef` | `str \| None` | ❌ | Retrievable reference (URL, storage key) |
| `reason` | `str \| None` | ❌ | Human-readable explanation |

**Validation Rules**:
- `prevReceiptDigest` MUST be 64 hex characters (SHA-256)
- Chain integrity verified by fetching predecessor and comparing eventDigest

---

### Binding

Links artifacts and messages to the event.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `artifacts` | `list[ArtifactBinding] \| None` | ❌ | Bound files/documents |
| `message` | `MessageBinding \| None` | ❌ | Bound email/message |

#### ArtifactBinding

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `str` | ✅ | Artifact identifier/filename |
| `digest` | `str` | ✅ | SHA-256 hex digest of artifact content |
| `mediaType` | `str \| None` | ❌ | MIME type |

#### MessageBinding

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `messageId` | `str` | ✅ | Email Message-ID or equivalent |
| `threadId` | `str \| None` | ❌ | Thread/conversation ID |

---

### Signature

Explicit cryptographic signature over the event.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `role` | `Literal["user", "agent", "authority"]` | ✅ | Signer role |
| `signer` | `str` | ✅ | Signer DID |
| `alg` | `Literal["ES256"]` | ✅ | Signature algorithm |
| `signed` | `SignedMetadata` | ✅ | What was signed |
| `jws` | `str` | ✅ | Detached JWS compact serialization |

#### SignedMetadata

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `canonicalization` | `Literal["RFC8785"]` | ✅ | Canonicalization method |
| `digest` | `str` | ✅ | SHA-256 hex of signed content |
| `target` | `Literal["/event"]` | ✅ | JSON pointer to signed content |

**Validation Rules**:
- `jws` format: `base64url(header)..base64url(signature)` (payload omitted)
- `signer` MUST match a `kid` in the resolved DID document
- `signed.digest` MUST equal `eventDigest`

---

## State Transitions

Events themselves are immutable. State changes are recorded as new receipts.

```
┌────────────────────┐
│  action.planned    │
└─────────┬──────────┘
          │
          ▼
┌────────────────────┐
│ approval.requested │ (optional, if policy requires)
└─────────┬──────────┘
          │
          ├──────────────────────────────┐
          ▼                              ▼
┌────────────────────┐      ┌────────────────────┐
│ approval.granted   │      │ approval.revoked   │
└─────────┬──────────┘      └────────────────────┘
          │                         │
          │                         │ (blocks execution)
          ▼                         │
┌────────────────────┐              │
│ action.executed    │◄─────────────┘ (if revocation.ts > decisionTs)
└────────────────────┘
          │
          │ (on failure)
          ▼
┌────────────────────┐
│  action.failed     │
└────────────────────┘
```

---

## JSON Serialization

All entities serialize to JSON using snake_case field names (Python convention converted
to camelCase for interoperability if needed).

**Example Receipt**:
```json
{
  "type": "MailioReceipt",
  "v": "v1",
  "eventDigest": "a1b2c3d4e5f6...",
  "event": {
    "type": "MailioAuditEvent",
    "v": "v1",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "flowId": "660f9400-f39c-51e5-b827-557766551111",
    "ts": "2026-02-04T15:30:00.123456+00:00",
    "actors": {
      "issuer": "did:web:agent.example.com",
      "subject": "did:web:user.example.com"
    },
    "intent": {
      "kind": "mailio.workflow.action.planned",
      "action": "invoice.send",
      "category": "financial",
      "scope": {
        "invoiceId": "INV-2026-001",
        "amount": 1500.00,
        "currency": "USD"
      }
    }
  },
  "signatures": [
    {
      "role": "agent",
      "signer": "did:web:agent.example.com",
      "alg": "ES256",
      "signed": {
        "canonicalization": "RFC8785",
        "digest": "a1b2c3d4e5f6...",
        "target": "/event"
      },
      "jws": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleS0xIn0..MEUCIQDx..."
    }
  ]
}
```
