DPDP Act 2023 and GDPR Article 32 both require appropriate technical measures — and the ability to demonstrate them. A screenshot of your code isn't evidence. The compliance report is a dated, signed-off inventory of exactly which fields are protected, which aren't, who accepted the risk, and what fired recently — and it writes itself on your schedule.
One generated document answers the questions your security review and auditor will actually ask — from the model's real state at generation time, not a policy claiming coverage.
| Entity | Property | Column | CLR type | Storage format |
|---|---|---|---|---|
| Customer | AadhaarNumber | aadhaar_number | String | Base64 |
| Customer | PanNumber | pan_number | String | Base64 |
| Payment | CardNumber | card_number | String | Binary |
| Payee | SettlementAccount | settlement_acct | String | Base64 |
| Severity | Entity.Property | Column | Evidence & recommendation |
|---|---|---|---|
| Critical | Customer.AadhaarNumber | aadhaar_bak | 94% of 100 samples pattern-verified. Encrypt or drop the backup column. |
| Warning | Lead.Notes | notes | ML-classified free-text PII in 21% of samples. |
| Entity | Property | Justification | Approved by |
|---|---|---|---|
| Payout | AccountLast4 | Masked at ingestion; only last 4 digits stored | CISO 2026-07-01 |
| Time (UTC) | Severity | Kind | Source |
|---|---|---|---|
| 2026-07-23 03:14:07 | Critical | AnomalyDetected | entity:Customer |
| 2026-07-23 09:00:05 | Warning | ScanFinding | scan:AppDbContext |
generator.ToHtml(report)
— A4 page rules included, so print-to-PDF is the whole workflow.Set the cadence and it generates and emails itself, with the report attached. Recipients are separate from your alert list by default — the report names every unprotected column in your schema, so it belongs with compliance rather than the on-call rota.
| Encryption coverage | 82.9% |
| Encrypted properties | 34 of 41 candidates |
| Critical findings | 1 |
| Accepted risks | 2 |
| Alerts in window | 14 |
| Dead-lettered deliveries | 0 |
Daily, weekly on a chosen weekday, or monthly on a chosen date — at your time of day, in your time zone.
Configured in appsettings.json, so changing it isn't a redeploy.
Unconfigured, it's the 1st of each month at 06:00 UTC.
Deploying at 06:00 on the 1st used to mean no report that month. Now the run is recorded, and a window missed while the process was down is sent late instead of skipped.
Instances sharing a volume claim the run atomically, so three pods send one report between them. A failed send releases the claim so it retries rather than consuming the month.
// appsettings.json "Report": { "Enabled": true, "Cadence": "Weekly", // Daily | Weekly | Monthly | Custom "DayOfWeek": "Monday", "TimeOfDay": "09:00:00", "TimeZoneId": "Asia/Kolkata", "Recipients": [ "dpo@acme.example" ] }
// Scan the live model and data, then render the evidence pack. ScanReport scan = await scanner.ScanAsync(context); ComplianceReport report = generator.Generate( context, "AES-GCM 256-bit, keys in Azure Key Vault, rotated 2026-06-01", scan, recentAlerts); await File.WriteAllTextAsync("compliance.html", generator.ToHtml(report)); await File.WriteAllTextAsync("compliance.json", generator.ToJson(report));
CoveragePercent drops.The report is generated from your live model and data — so what you hand the auditor is what's actually true at that moment. Pairs with the alerting layer that tells you the moment something changes.