> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Governance

> Manage actor PII mappings, download completed audit-log archives, and re-verify Matcher's tamper-evident audit hash chain — the compliance surface for GDPR and audit.

Matcher's governance surface groups the three compliance-facing capabilities under `/v1/governance`: **actor mappings** (link opaque actor IDs to PII, with GDPR pseudonymize/erasure), **archives** (download completed audit-log archives from cold storage), and **audit logs** (immutable, hash-chained history you can independently re-verify). The tenant is always resolved from the JWT — never from a path or body.

<Note>Every governance route is scoped to the caller's tenant. Actor-mapping reads are split into two authorization tiers: the PII-free list versus the single-record de-anonymization read, so identity resolution stays separable from browse access.</Note>

## Actor mappings

***

An actor mapping links an opaque `actorId` (for example `user:550e8400-e29b-41d4-a716-446655440000`) to human-readable PII (`displayName`, `email`). List rows are **PII-free by design** — only the single-record `GET` returns cleartext identity.

### List actor mappings

Cursor-paginated, PII-free rows. Filter by an actor-ID prefix.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/actor-mappings?actorId=user:&limit=25" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "items": [
    {
      "actorId": "user:550e8400-e29b-41d4-a716-446655440000",
      "createdAt": "2026-01-15T10:30:00Z",
      "updatedAt": "2026-01-15T10:30:00Z"
    }
  ],
  "limit": 25,
  "nextCursor": "",
  "hasMore": false
}
```

Query parameters: `actorId` (prefix filter), `limit` (default 25, capped at 100), and `cursor`.

### Upsert an actor mapping

Creates or updates the PII for an actor ID. `PUT` is idempotent — the same call creates the record on first use and updates it thereafter. At least one of `displayName` or `email` must be supplied.

```bash theme={null}
curl -X PUT "https://api.matcher.example.com/v1/governance/actor-mappings/{actorId}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "John Doe",
    "email": "john.doe@example.com"
  }'
```

```json theme={null}
{
  "actorId": "user:550e8400-e29b-41d4-a716-446655440000",
  "displayName": "John Doe",
  "email": "john.doe@example.com",
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-01-15T10:30:00Z"
}
```

### Get one actor mapping (de-anonymize)

Returns the cleartext PII for a single actor ID. This **is** the de-anonymization primitive, so it is gated behind the narrower `deanonymize` permission rather than plain read.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/actor-mappings/{actorId}" \
  -H "Authorization: Bearer $TOKEN"
```

### Pseudonymize (GDPR)

Replaces `displayName` and `email` with `[REDACTED]` while preserving the record and its `actorId` link — the audit trail stays intact but the PII is gone. Responds `204 No Content`.

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/governance/actor-mappings/{actorId}/pseudonymize" \
  -H "Authorization: Bearer $TOKEN"
```

### Delete (right to erasure)

Permanently removes the mapping for GDPR right-to-erasure. Responds `204 No Content`.

```bash theme={null}
curl -X DELETE "https://api.matcher.example.com/v1/governance/actor-mappings/{actorId}" \
  -H "Authorization: Bearer $TOKEN"
```

<Note>Pseudonymize keeps the record (PII scrubbed); delete removes it entirely. Choose pseudonymize when you must retain the audit linkage, delete when the record itself must not persist.</Note>

## Archives

***

When audit-log partitions age out, they are compressed and moved to object storage. The archives endpoints list completed archives and issue time-limited download URLs.

### List archives

Offset-paginated. Filter by date range.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/archives?from=2024-01-01&to=2024-03-31&limit=20&offset=0" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "partitionName": "audit_logs_2024_q1",
      "dateRangeStart": "2024-01-01T00:00:00Z",
      "dateRangeEnd": "2024-03-31T23:59:59Z",
      "rowCount": 150000,
      "compressedSizeBytes": 10485760,
      "storageClass": "GLACIER",
      "checksum": "sha256:abc123def456...",
      "status": "COMPLETE",
      "archivedAt": "2024-04-01T02:30:00Z"
    }
  ],
  "limit": 20,
  "hasMore": true
}
```

Query parameters: `from`, `to` (`YYYY-MM-DD` or RFC 3339), `limit` (1–200, default 20), and `offset`. Only `COMPLETE` archives are listed — in-progress and failed archives are never surfaced.

### Download an archive

Returns a presigned URL plus the checksum for integrity verification.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/archives/{id}/download" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "downloadUrl": "https://s3.amazonaws.com/bucket/archive.gz?X-Amz-Signature=...",
  "expiresAt": "2026-02-05T13:00:00Z",
  "checksum": "sha256:abc123def456..."
}
```

<Warning>Archives in `GLACIER` or `DEEP_ARCHIVE` storage classes require a restore before the download URL resolves. `STANDARD`, `STANDARD_IA`, `ONEZONE_IA`, `INTELLIGENT_TIERING`, and `GLACIER_IR` are immediately readable.</Warning>

## Audit logs

***

Every governance-relevant change is written to an immutable, per-tenant audit log. Each record is linked into a tamper-evident SHA-256 hash chain (`recordHash` = `SHA-256(prevHash || canonical content)`), so a client can independently re-verify integrity.

### List audit logs

Cursor-paginated, with rich filters.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/audit-logs?actor=user@example.com&action=CREATE&entity_type=context&date_from=2025-01-01&date_to=2025-01-31&limit=20" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "tenantId": "550e8400-e29b-41d4-a716-446655440001",
      "entityType": "reconciliation_context",
      "entityId": "550e8400-e29b-41d4-a716-446655440002",
      "action": "CREATE",
      "actorId": "user@example.com",
      "changes": { },
      "truncated": false,
      "originalSize": 0,
      "createdAt": "2025-01-15T10:30:00Z",
      "tenantSeq": 42,
      "recordHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "prevHash": "0000000000000000000000000000000000000000000000000000000000000000",
      "hashVersion": 1
    }
  ]
}
```

Query parameters: `actor`, `action`, `entity_type`, `date_from`, `date_to` (`YYYY-MM-DD` or RFC 3339), `limit` (1–200, default 20), and `cursor`.

When a diff exceeds the outbox payload cap, `changes` carries a truncation-marker envelope instead of the full diff, and `truncated` becomes `true` with `originalSize` reporting the pre-truncation byte size.

### Verify the audit chain

Re-verifies that every inspected record links to the previous one and matches its stored hash. This is strictly read-only — it proves tamper-evidence, never mutating a record.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/audit-logs/verify?maxRecords=10000" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "intact": true,
  "verifiedCount": 1024,
  "firstBrokenSeq": 0,
  "truncated": false
}
```

`intact` is `true` when the whole inspected span is unbroken; if a break is found, `firstBrokenSeq` reports the `tenantSeq` of the first failing record and `verifiedCount` reports how many held before it. `truncated` is `true` when the chain holds more records than the `maxRecords` inspection bound allowed.

### Get one audit log

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/audit-logs/{id}" \
  -H "Authorization: Bearer $TOKEN"
```

<Note>You can also list an entity's history directly with `GET /v1/governance/entities/{entityType}/{entityId}/audit-logs` (cursor-paginated), which is convenient when you already know the entity you are auditing.</Note>

## Response codes

***

| Status | Meaning                                                                         |
| ------ | ------------------------------------------------------------------------------- |
| `200`  | Mapping, archive, or audit data returned                                        |
| `204`  | Actor mapping pseudonymized or deleted                                          |
| `400`  | Invalid input (missing displayName/email, bad date/pagination)                  |
| `403`  | Missing the required permission tier (e.g. `deanonymize` for single-record PII) |
| `404`  | Actor mapping, archive, or audit log not found                                  |
| `422`  | Malformed field (e.g. invalid email format)                                     |
