# Sanctions Root Attestation — Operator Guide

## Overview

The Sanctions Root Attestation feed (chunk-7) enables admitted §48.37 Compliance Root Authorities to publish periodic Merkle-root anchors of all sanctioned identifiers. Each Sanctions Root Attestation (SRA) is a Poseidon2 SMT root that consumers can use to verify non-inclusion proofs for counterparty screening.

SRA Tag: `0x54485260` (per THREAD §8.8 / MASTER-INDEX).  
Schema: `platform/db/schema/37-sanctions-root.sql`.  
Publication service: v0.2.116 (broadcaster; GossipSub topic class TBD — spec gap).

---

## Lifecycle

```
Founder:  admit_sanctions_root_authority (founder_signed_envelope_hex — §6.13a Cluster A COSE_Sign1)
                   ↓ authority_state=0 (active)
Authority: publish_sanctions_root  →  broadcast_status=0 (pending)
Pub svc:   scans pending rows       →  broadcast_status=1 (broadcast_ok)  ← GossipSub (TBD)
                                    →  broadcast_status=2 (broadcast_failed) ← retry
Consumer:  query_sanctions_root / list_sanctions_roots
           → ZK Non-Inclusion Proof (deferred; tag 0x54485261)
```

---

## Tools

### `thread.admit_sanctions_root_authority`

Admit a new §48.37 Compliance Root Authority into the Sanctions Root Authority Registry.

**Founder-authorized only — NO self-admission.** Admission requires a **§6.13a Cluster A `COSE_Sign1` envelope signed by the FOUNDER key** (mirrors `thread.admit_settlement_venue` / `thread.admit_authority`). The legacy self-admit path — where any agent could submit its own `secret_key_hex` plus raw parameters and register *itself* as a Compliance Root Authority — is **removed (SEC-A2-05)**. The tool now accepts a single parameter; the bridge verifies the Founder signature over the canonical CBOR map before admitting and rejects any envelope not signed by the registered Founder key.

**Required param:**

| Param | Type | Description |
|---|---|---|
| `founder_signed_envelope_hex` | string | Hex of a §6.13a Cluster A `COSE_Sign1`, signed by the **Founder key**, over the canonical CBOR map below. Admission is rejected unless this signature verifies against the registered Founder key. |

**Envelope CBOR map (canonical; integer keys):**

| Key | Field | Type | Description |
|---|---|---|---|
| `0` | _(reserved)_ | — | Reserved doc-tag sentinel. |
| `1` | `created_slot` | uint | Slot at which the envelope was created. |
| `2` | `effective_slot` | uint | Slot the admission takes effect. MUST be ≥ `created_slot + EFFECTIVE_LEAD_SLOTS_MIN`. |
| `3` | `authority_agent_id` | bytes(32) | Agent ID of the authority being admitted. |
| `4` | `covered_list_ids` | [uint] | List IDs this authority covers (e.g. `[0, 1]` for OFAC SDN + EU Consolidated). |
| `5` | `lists_mask` | uint | Bitmask of covered lists. |
| `6` | `primary_regime_count` | uint | Primary-regime list count (~24min cadence). Default 0. |
| `7` | `secondary_regime_count` | uint | Secondary-regime list count (~24h cadence). Default 0. |
| `8` | `sra_cadence_slots` | uint | Authority-declared SRA publication cadence in Solana slots (> 0). |
| `9` | `authority_public_key` | bytes(32) | SRA-signature verify key (Ed25519). |
| `10` | `authority_bond_escrow_pda` | bytes(32) | Solana PDA of the locked admission bond. |
| `11` | `authority_bond_micro_cosr` | uint | Bond amount in µCOSR (> 0). |
| `12` | `supported_zk_proof_systems_mask` | uint | ZK proof systems bitmask (0 = none; ZK deferred). Default 0. |
| `13` | `methodology_hash` | bytes(32) | SHA-256 of the screening methodology document. |
| `14` | `max_nullifier_age_slots` | uint | Max age (slots) of a nullifier included in the Merkle tree (> 0). |
| `15` | `registry_signature` | bytes | COSE_Sign1 registry signature. |
| `16` | `authority_co_sign` | bytes | Co-signature for primary-regime authorities (optional). |

**Success response:**

```json
{
  "authority_id": "1",
  "authority_agent_id_hex": "<64-char hex>",
  "lists_mask": "3",
  "authority_state": 0,
  "status": "admitted"
}
```

---

### `thread.query_sanctions_root_authority`

Fetch a single admitted authority by `authority_id`.

**Params:** `{ "authority_id": <integer> }`

**Success response:** Full authority record including `covered_list_ids`, bond info, methodology_hash, cadence, and state.

**Error:** `sanctions_authority_not_found` if ID not found.

---

### `thread.list_sanctions_root_authorities`

List admitted authorities with optional filters.

**Optional params:**

| Param | Type | Description |
|---|---|---|
| `authority_state` | number | Filter by state: 0=active, 1=suspended, 2=retired. |
| `lists_mask_intersect` | number | Filter to authorities whose `lists_mask` shares at least one bit with this value. |
| `limit` | number | Max rows (default 20, max 100). |
| `offset` | number | Pagination offset (default 0). |

**Success response:** `{ "authorities": [...], "total": N }`

---

### `thread.publish_sanctions_root`

Publish a new §8.8 Sanctions Root Attestation (tag 0x54485260). The authority must be active.

**Required params:**

| Param | Type | Description |
|---|---|---|
| `secret_key_hex` | string | 32-byte Ed25519 seed (hex). Used for dev-stub `issuer_signature` if omitted. |
| `authority_id` | number | Admitted authority's `authority_id`. |
| `merkle_root_hex` | string | 64-char hex (32 bytes) Poseidon2 SMT root of all sanctioned identifiers. |
| `snapshot_slot` | number | Solana slot of the sanctions list snapshot. MUST be > previous SRA `snapshot_slot` per I84. |
| `lists_mask` | number | Bitmask of sanctions lists covered by this SRA. |
| `leaf_count` | number | Count of sanctioned identifiers. Must not drop > 5% vs previous (I84). |
| `expiry_slot` | number | Slot after which this SRA is no longer fresh (> `snapshot_slot`). |
| `list_manifest_hash_hex` | string | 64-char hex (32 bytes) SHA-256 of the list manifest. |

**Optional params:**

| Param | Type | Default | Description |
|---|---|---|---|
| `commitment_scheme_id` | number | 0 | 0 = Poseidon2 (default). |
| `prev_sra_id_hex` | string | `""` | 64-char hex for chained SRA; empty string for genesis. Must reference most recent SRA (I84). |
| `primary_regime_count` | number | 0 | Primary-regime list count. |
| `secondary_regime_count` | number | 0 | Secondary-regime list count. |
| `authority_co_sign_hex` | string | — | Co-signature; required when `primary_regime_count > 0`. |
| `chain_confirmation_policy` | number | 3 | Must be 3 (finalized) per §8.8. |
| `zk_circuit_vk_hash_hex` | string | — | VK hash for ZK non-inclusion proofs (deferred). |
| `nullifier_bloom_filter_hex` | string | — | Optional Bloom filter for fast non-inclusion pre-check. |
| `issuer_signature_hex` | string | dev stub | COSE_Sign1 bytes by authority's signing key. |
| `sra_id_hex` | string | random | 64-char hex SRA ID. |

**Success response:**

```json
{
  "sra_id_hex": "<64-char hex>",
  "authority_id": "1",
  "authority_agent_id_hex": "<64-char hex>",
  "snapshot_slot": "4450021600",
  "lists_mask": "3",
  "leaf_count": "10000",
  "broadcast_status": 0
}
```

---

### `thread.query_sanctions_root`

Fetch a §8.8 SRA. Two modes:

**Mode 1 — by `sra_id_hex`:** returns the specific SRA.

**Mode 2 — by `(authority_id, lists_mask)`:** returns the most recent SRA for that pair.

**Params:**
- Mode 1: `{ "sra_id_hex": "<64-char hex>" }`
- Mode 2: `{ "authority_id": <int>, "lists_mask": <int> }`

**Success response:** Full SRA record (all §8.8 fields + `broadcast_status`, `created_at`).

**Errors:** `sra_not_found`, `sanctions_authority_not_found`.

---

### `thread.list_sanctions_roots`

List SRAs with optional filters. Results ordered by `snapshot_slot DESC`.

**Optional params:**

| Param | Type | Description |
|---|---|---|
| `authority_id` | number | Filter by authority. |
| `lists_mask` | number | Filter by exact `lists_mask`. |
| `broadcast_status` | number | Filter by broadcast_status (0=pending, 1=broadcast_ok, 2=broadcast_failed). |
| `limit` | number | Max rows (default 20, max 100). |
| `offset` | number | Pagination offset (default 0). |

**Success response:** `{ "attestations": [...], "count": N }`

---

## Authority states

| Value | Name | Meaning |
|---|---|---|
| `0` | `active` | Authority is admitted and may publish SRAs. |
| `1` | `suspended` | Authority is suspended; new publications rejected. |
| `2` | `retired` | Authority is retired (terminal; cannot revert). |

---

## Broadcast status values

| Value | Name | Meaning |
|---|---|---|
| `0` | `pending_broadcast` | Row created; publication service has not yet run. |
| `1` | `broadcast_ok` | Successfully published to GossipSub. |
| `2` | `broadcast_failed` | Last broadcast attempt failed; service will retry. |

Publication service (v0.2.116) transitions `0 → 1` or `0 → 2` on each cycle.

---

## I84 invariants (enforced at write time)

The `publish_sanctions_root` handler and the DB trigger jointly enforce:

1. **snapshot_slot strict-monotonicity:** Each SRA's `snapshot_slot` MUST be > previous for same `(authority_id, lists_mask)`. Rejection: `SRA_SNAPSHOT_SLOT_REGRESSION`.
2. **leaf_count drop bound:** Drop > 5% (500 bps) of previous `leaf_count` rejected. Rejection: `SRA_LEAF_COUNT_DECREASE_EXCESSIVE`.
3. **prev_sra_id chain continuity:** Non-genesis SRAs must reference the most recent SRA via `prev_sra_id_hex`. Rejection: `SRA_CHAIN_BROKEN`.
4. **Genesis constraint:** First SRA for an `(authority_id, lists_mask)` pair must have empty `prev_sra_id_hex`. Rejection: `SRA_CHAIN_BROKEN`.

---

## SRA freshness windows

| Regime | Cadence (slots) | Max staleness (slots) | Approx. |
|---|---|---|---|
| Primary | 21,600 | 43,200 | ~24min cadence / ~4h max |
| Secondary | 216,000 | 432,000 | ~24h cadence / ~48h max |

---

## Error catalog

| Error code | Cause |
|---|---|
| `sanctions_authority_not_found` | No admitted authority with given `authority_id`. |
| `sanctions_authority_already_admitted` | `authority_agent_id` (envelope field `3`) is already admitted. |
| `sra_authority_not_active` | Authority exists but `authority_state != 0` (suspended or retired). |
| `SRA_AUTHORITY_ROLE_BIT_MISSING` | Authority with given `authority_id` not found in registry. |
| `SRA_SNAPSHOT_SLOT_REGRESSION` | `snapshot_slot` <= previous SRA's `snapshot_slot` for `(authority, lists_mask)` (I84). |
| `SRA_LEAF_COUNT_DECREASE_EXCESSIVE` | `leaf_count` drop > 5% from previous SRA (I84). |
| `SRA_CHAIN_BROKEN` | `prev_sra_id_hex` does not reference most recent SRA (I84 chain continuity). |
| `sra_not_found` | No SRA with given `sra_id_hex` or for given `(authority_id, lists_mask)`. |

---

## Typical operator flow

```bash
# 1. Admit authority — Founder-signed §6.13a Cluster A envelope only (NO self-admission, SEC-A2-05)
curl -s http://127.0.0.1:9401/mcp/invoke -d '{
  "tool": "thread.admit_sanctions_root_authority",
  "params": {
    "founder_signed_envelope_hex": "<hex of Founder-signed §6.13a Cluster A COSE_Sign1 over the canonical CBOR map>"
  }
}'

# 2. Publish genesis SRA
curl -s http://127.0.0.1:9401/mcp/invoke -d '{
  "tool": "thread.publish_sanctions_root",
  "params": {
    "secret_key_hex": "<32-byte-hex>",
    "authority_id": 1,
    "merkle_root_hex": "<32-byte-hex>",
    "snapshot_slot": 4450000000,
    "lists_mask": 3,
    "leaf_count": 10000,
    "expiry_slot": 4450043200,
    "list_manifest_hash_hex": "<32-byte-hex>",
    "prev_sra_id_hex": ""
  }
}'

# 3. Query latest SRA for authority 1 / lists_mask 3
curl -s http://127.0.0.1:9401/mcp/invoke -d '{
  "tool": "thread.query_sanctions_root",
  "params": { "authority_id": 1, "lists_mask": 3 }
}'
```

---

## Operator scripts (v0.2.117)

Two CLI scripts for one-shot operator use:

### `admit-sanctions-root-authority.ts`

Admits a new §48.37 authority into the registry. Because admission is **Founder-authorized only**
(SEC-A2-05), the script assembles the §6.13a Cluster A canonical CBOR map from the field flags,
has it signed by the **Founder key**, and submits the resulting `founder_signed_envelope_hex` —
there is no self-admit path (the old `--authority-key` self-derivation flag is gone; the authority's
agent ID is now an explicit envelope field).

```bash
tsx platform/scripts/admit-sanctions-root-authority.ts \
  --bridge http://127.0.0.1:9401 \
  --authority-agent-id <32-byte-hex> \
  --authority-pubkey <32-byte-hex> \
  --covered-list-ids 0,1 \
  --lists-mask 3 \
  --sra-cadence-slots 21600 \
  --authority-bond-pda <32-byte-hex> \
  --authority-bond-micro-cosr 10000000 \
  --methodology-hash <32-byte-hex> \
  --max-nullifier-age-slots 432000 \
  [--primary-regime-count 1] \
  [--dry-run]
```

Audit log: `platform/var/admit-sanctions-root-authority/<authority_id>.json`

### `publish-sanctions-root.ts`

Manually publishes a §8.8 SRA for an admitted authority.

```bash
tsx platform/scripts/publish-sanctions-root.ts \
  --bridge http://127.0.0.1:9401 \
  --authority-id 1 \
  --authority-key <32-byte-hex> \
  --merkle-root <32-byte-hex> \
  --snapshot-slot 4450000000 \
  --lists-mask 3 \
  --leaf-count 10000 \
  --expiry-slot 4450043200 \
  --list-manifest-hash <32-byte-hex> \
  [--prev-sra-id <64-hex>] \
  [--dry-run]
```

Audit log: `platform/var/publish-sanctions-root/<sra_id>.json`

**Automated cadence:** For continuous SRA publication in dev, use the broadcaster service
(`scripts/dev-sanctions-root-broadcaster.ts`, v0.2.116, Phase 11 in `topology-up.sh`).

---

## Deferred scope

| Item | Status |
|---|---|
| ZK Non-Inclusion Proof (tag 0x54485261) | Out of chunk-7; Groth16/BN254 consumer-side work |
| §49.1 Sanctions Screening Attestation (tag 0x5448522F) | Out of chunk-7; broader COMPLIANCE_PROVIDER admission |
| GossipSub topic class for SRA broadcasts | Spec gap; topic assignment at GossipSub integration pass |
| HSM-backed `issuer_signature` | Dev stub; COSE_Sign1 production signing deferred to post-v0.3 |

---

## Cross-region visibility

`sanctions_root_authorities`, `sanctions_root_authority_lists`, and `sanctions_root_attestations` are all in `pub_discovery_global` (v0.2.114, ADR-2026-0153). Any region can verify SRA authority admission and query the latest root without a cross-region round-trip.

---

## Chunk-7 milestones

| Version | Deliverable |
|---|---|
| v0.2.114 | §48.37 authority registry + §8.8 SRA schema + cross-region pub (ADR-2026-0153) |
| v0.2.115 | 6 HL bridge tools: admit/query/list authority + publish/query/list SRA (ADR-2026-0154) |
| v0.2.116 | Dev broadcaster service: 2 stub authorities per region, 60-120s loop (ADR-2026-0155) |
| v0.2.117 | Operator scripts: admit-sanctions-root-authority + publish-sanctions-root (ADR-2026-0156) |
| v0.2.118 | E2e smoke 37/37 PASS + chunk-7 close (ADR-2026-0157) |

**Chunk-8+:** §48.10 + §48.16 venue/platform cadence monitor. Post-chunk-9: ZK Non-Inclusion Proof consumer, §49.1 conventional screening, real Poseidon2 root.

---

## Protocol anchors

- §8.8 + §48.37 spec: `thread/08b-core-trade-compliance.md`
- I84 + I85 invariants: `thread/22b-protocol-invariants-v02-i82-i115.md`
- ADR-2026-0153 (v0.2.114): schema + cross-region pub
- ADR-2026-0154 (v0.2.115): HL tool surface
- ADR-2026-0155 (v0.2.116): broadcaster service (scheduled)
- ADR-2026-0156 (v0.2.117): operator scripts (scheduled)
- ADR-2026-0157 (v0.2.118): e2e smoke + chunk-7 close (scheduled)
