# Compliance Report Guide — Corvin

> **Audience:** Operator + DPO. Explains how to generate and
> interpret the monthly compliance report.

The report is produced by the `corvin-compliance-reports` plugin
(`core/compliance/corvin_compliance_reports/`). This guide describes
what each section means in terms of the layer architecture (L34–L37).

---

## 1. Generating the report

```bash
voice-audit compliance-report \
    --start 2026-04-01 --end 2026-04-30 \
    --output /tmp/corvin-compliance-2026-04.pdf
```

Recommended for `cron`-driven monthly automation. The PDF content is
generated by the `corvin-compliance-reports` CLI
(`python -m corvin_compliance_reports.cli`).

---

## 2. Report sections

The generated PDF carries five sections, each backed by a structural
audit-event family.

### 2.1 Section A — EU AI Act Art. 50 (Disclosure)

| Metric | Source | Healthy range |
|---|---|---|
| Disclosure cards shown | `disclosure.shown` events | ≥ 1 per new uid per (channel, chat) |
| `/join` self-registrations | `disclosure.joined` | informational |
| Opt-outs via `/leave` | `disclosure.left` | informational |

**Red flag:** disclosure card emitted ZERO times in a period where
new users joined → L19 disclosure module disabled or broken.

### 2.2 Section B — GDPR Art. 30 (Records of Processing)

| Metric | Source | Healthy range |
|---|---|---|
| Total engine spawns | `engine.spawn_started` | matches operator's expected load |
| Per-engine breakdown | grouped by `engine_id` | EU_PRODUCTION: only allowed engines |
| Per-classification breakdown | `data_flow.approved` grouped by `classification` | PUBLIC/INTERNAL bulk; CONFIDENTIAL/SECRET rare |

**Red flag:** any `engine_id` outside `data_residency.allowed_engines`
appears → preset misconfiguration.

### 2.3 Section C — GDPR Art. 32 (Security)

| Metric | Source | Healthy range |
|---|---|---|
| Audit chain integrity | daily `voice-audit verify` exit codes | 100 % rc == 0 |
| `audit.integrity_violation` | event count | **0** |
| `path_gate.denied` | event count | a few per day = healthy (path-gate active); 0 might mean disabled |
| `consent.denied` | event count | matches expected refusal patterns |
| `data_flow.blocked` | event count (CRITICAL) | 0 in normal operation; spikes indicate misconfiguration or attempted misuse |
| `egress.blocked` | event count (CRITICAL) | 0 in normal operation; spikes indicate attempted misuse |
| `audit.rotation_failed` | event count (CRITICAL) | **0** |

**Red flags:**

* Non-zero `audit.integrity_violation` → URGENT investigation;
  someone tampered with the chain.
* Non-zero `audit.rotation_failed` → sealer (age / gpg) broken;
  audit not being persisted to disk.
* High spike of `data_flow.blocked` / `egress.blocked` → either
  user is hitting policy walls (expected) or attacker is probing
  (investigate).

### 2.4 Section D — GDPR Art. 17 (Erasure Trail)

| Metric | Source | Healthy range |
|---|---|---|
| Erasure requests | `erasure.requested` | matches DPO inbox |
| Successful erasures | `erasure.completed` with `overall_status=completed` | should equal Section D row 1 minus partial / failed |
| Partial erasures | `erasure.completed` with `overall_status=partial` | document why per-handler failure happened |
| Failed erasures | `erasure.completed` with `overall_status=failed` | **0** |
| Stub-only erasures | `erasure.skipped` count when handler reason mentions "stub" | ideally 0 once real handlers ship |

**Red flag:** non-zero failed erasures → DPO followed-up required;
identify the failed layer + retry.

### 2.5 Section E — Audit chain attestation

| Metric | Source | Healthy range |
|---|---|---|
| Live segment current tail-hash | `audit.jsonl` last line | non-empty |
| Sealed segments in period | rotated files | matches rotation policy |
| Sealed-segment integrity | unseal + verify | 100 % pass |
| Retention sweeps | `audit.segment_retired` | matches retention_years |
| Unseal requests | `audit.unseal_requested` | matches DPO inspection log |

**Red flag:** any sealed segment fails post-unseal `verify_chain`.

---

## 3. Interpreting CRITICAL audit-event spikes

The following audit-event types are CRITICAL severity. Each spike
deserves operator attention:

| Event | Likely cause | First action |
|---|---|---|
| `audit.integrity_violation` | tamper attempt OR disk corruption | freeze the chain (chmod 0400 live file), bring up backup, investigate |
| `audit.rotation_failed` | sealer binary missing or recipient invalid | check `age` / `gpg` install + recipient validity |
| `data_flow.blocked` | misconfigured tenant matrix OR attempted misuse | sample 5 events, look at `engine_id` × `classification` → diagnose |
| `egress.blocked` | engine attempting to reach forbidden host OR allowed_hosts forgot a legitimate host | sample 5 events, check `host` field |
| `erasure.failed` | per-layer handler raised | check the `reason` field, fix the handler, retry the erasure |
| `path_gate.self_test_failed` | path-gate hook misregistered at boot | check `hooks/hooks.json` + restart adapter |
| `boot.self_test_failed` | core self-test detected an invariant violation | check `bridge.sh doctor --strict --json` for the failing check |

---

## 4. Automation pattern

Recommended cron entry on the operator's host:

```cron
# Monthly compliance report at 03:00 on the first of the month
0 3 1 * * /usr/local/bin/voice-audit compliance-report \
    --start "$(date -d 'last month' +%Y-%m-01)" \
    --end "$(date -d 'last month' +%Y-%m-%d)" \
    --output /var/corvin/reports/compliance-$(date -d 'last month' +%Y-%m).pdf \
    && mail -s "Corvin compliance report $(date -d 'last month' +%Y-%m)" \
           -a /var/corvin/reports/compliance-$(date -d 'last month' +%Y-%m).pdf \
           dpo@example.com
```

SIEM forwarding (separate concern; not in this guide): forward
every CRITICAL audit event in real-time so the DPO doesn't wait for
the monthly report to learn about an integrity violation.

---

## 5. Glossary

* **EU_PRODUCTION preset** — `tenant.corvin.eu-production-{ollama,http}.yaml`.
  Ships the three-layer defence (identity + classification + egress).
* **Three-layer defence** — see the L35 egress lockdown docs.
* **subject_id** — pseudonymous user identifier per L36 regex.
* **sealed segment** — rotated `audit.<stamp>.jsonl.{age,gpg}`.
* **chain link** — the `prev_hash` reference connecting consecutive
  audit entries, including across rotation via `audit.rotation_link`.

---

## 6. References

* `docs/claude-ref/layer-34-data-classification.md` — Data Classification
* `docs/claude-ref/layer-35-egress-lockdown.md` — Egress Lockdown
* `docs/claude-ref/layer-37-audit-at-rest.md` — Audit-at-rest
* `docs/claude-ref/layer-36-erasure.md` — Erasure
* `docs/compliance/DSB-CHECKLIST.md` — DPO checklist
* `docs/compliance/DPIA-TEMPLATE.md` — DPIA template
