Skip to content

PII Data Map

Tayra provides a built-in PII inventory service that generates a comprehensive data map of every personal data field in your application. This is your primary tool for GDPR Article 30 compliance -- the Records of Processing Activities that every data controller is required to maintain. The inventory reads from Tayra's metadata caches at runtime, producing a structured report that you can hand directly to your DPO, auditor, or supervisory authority.

Why This Matters

GDPR Article 30 requires every data controller to maintain a written record of processing activities. In practice, most organizations maintain these records in spreadsheets that go stale within weeks of being created. Tayra's PII inventory is generated from your actual code -- the same [PersonalData], [DataSubjectId], and [BlindIndex] attributes that drive encryption at runtime also drive the inventory. When you add a new PII field, the inventory updates automatically. When you remove one, it disappears from the report. There is no separate document to maintain.

This is especially valuable during audits. Instead of assembling evidence from multiple systems, you generate a single report that shows exactly which entities contain personal data, which fields are encrypted, which have blind indexes for searchable encryption, and which integrations protect them.

Setup

Package Requirement

This feature requires the Tayra.Compliance package and a Compliance edition license. Data protection itself is handled by Tayra.Core — this package provides the reporting tooling.

shell
dotnet add package Tayra.Compliance

Register the compliance services after calling AddTayra(). List every entity type that contains personal data:

csharp
using Tayra.Compliance;

services.AddTayra(opts => opts.LicenseKey = licenseKey)
    // InMemoryKeyStore is used by default for development.
    // For production, chain a secrets manager: .UseVaultKeyStore(...)
    .AddCompliance(complianceOpts =>
    {
        complianceOpts.ApplicationName = "MyApp";
        complianceOpts.AddEntityType<Customer>();
        complianceOpts.AddEntityType<Order>();
        complianceOpts.AddEntityType<Employee>();
        complianceOpts.AddEntityType<SupportTicket>();
    });

The AddEntityType<T>() method registers a type for scanning. Only registered types appear in the inventory. This is intentional -- it prevents the inventory from surfacing internal types that are not part of your processing activities.

PiiInventoryOptions

PropertyTypeDefaultDescription
ApplicationNamestring"Application"Name used in report headers
MethodDescription
AddEntityType<T>()Registers an entity type for PII scanning (fluent, returns PiiInventoryOptions)
AddEntityType(Type type)Registers an entity type by Type reference

Generating a Report

Use ITayraCompliance.GenerateInventory() to get a structured PiiInventoryReport:

csharp
public class ComplianceController : ControllerBase
{
    private readonly ITayraCompliance _compliance;

    public ComplianceController(ITayraCompliance compliance)
    {
        _compliance = compliance;
    }

    [HttpGet("/compliance/pii-inventory")]
    public IActionResult GetInventory()
    {
        var report = _compliance.GenerateInventory();
        return Ok(report);
    }
}

The GenerateReport() method reads from PersonalDataMetadataCache and BlindIndexMetadataCache to produce the report. These caches are populated automatically when Tayra scans your entity types at startup.

Report Structure

PiiInventoryReport

The top-level report object:

PropertyTypeDescription
GeneratedAtDateTimeOffsetTimestamp when the report was generated
ApplicationNamestringApplication name from PiiInventoryOptions
EntitiesIReadOnlyList<PiiEntityEntry>All entity types containing personal data
SummaryPiiInventorySummaryAggregate statistics

PiiEntityEntry

Each entity type that contains personal data:

PropertyTypeDescription
EntityTypestringThe CLR type name (e.g., "Customer")
FullTypeNamestringFully qualified type name including namespace
DataSubjectsIReadOnlyList<PiiDataSubjectEntry>Data subject identifier fields on this entity
FieldsIReadOnlyList<PiiFieldEntry>Personal data fields
BlindIndexesIReadOnlyList<PiiBlindIndexEntry>Blind indexes configured on this entity
ProtectedByIReadOnlyList<string>Integration names protecting this entity (e.g., "EF Core", "Marten")
IsFullyEncryptedbooltrue when all PII fields have encryption configured

PiiFieldEntry

Each personal data field:

PropertyTypeDescription
PropertyNamestringThe property name on the entity
FieldKindstringThe kind of personal data: Text, Deep, Serialized
Groupstring?The data subject group this field belongs to
IsEncryptedboolWhether this field is encrypted by Tayra
HasBlindIndexboolWhether this field has a blind index for searchable encryption
Replacementstring?The mask value used when crypto-shredding

PiiDataSubjectEntry

Each data subject identifier on the entity:

PropertyTypeDescription
PropertyNamestringThe property name marked with [DataSubjectId]
Groupstring?The data subject group
Prefixstring?The key prefix for this data subject

PiiBlindIndexEntry

Each blind index configured on the entity:

PropertyTypeDescription
IndexNamestringThe name of the blind index
SourcePropertystringThe source property (or +-joined names for compound indexes)
IsCompoundboolWhether this is a compound index spanning multiple fields
TransformsIReadOnlyList<string>Transform names applied before hashing (e.g., Lowercase, Trim)

PiiInventorySummary

Aggregate statistics across all registered entity types:

PropertyTypeDescription
TotalEntityTypesintNumber of entity types with PII
TotalPiiFieldsintTotal personal data fields across all entities
EncryptedFieldsintNumber of fields with encryption
BlindIndexedFieldsintNumber of fields with blind indexes
FullyEncryptedEntitiesintEntities where every PII field is encrypted
PartiallyEncryptedEntitiesintEntities where some PII fields lack encryption
IntegrationCountintNumber of active integration providers

Export Formats

JSON Export

Use ExportAsJson() to get a machine-readable JSON representation:

csharp
var json = _compliance.ExportInventoryAsJson();
await File.WriteAllTextAsync("pii-inventory.json", json);

The JSON output uses camelCase property names and omits null values:

json
{
  "generatedAt": "2026-03-10T14:30:00+00:00",
  "applicationName": "MyApp",
  "entities": [
    {
      "entityType": "Customer",
      "fullTypeName": "MyApp.Models.Customer",
      "dataSubjects": [
        { "propertyName": "CustomerId", "prefix": "cust-" }
      ],
      "fields": [
        {
          "propertyName": "FullName",
          "fieldKind": "Text",
          "isEncrypted": true,
          "hasBlindIndex": true,
          "replacement": "[REDACTED]"
        },
        {
          "propertyName": "Email",
          "fieldKind": "Text",
          "isEncrypted": true,
          "hasBlindIndex": true,
          "replacement": "redacted@example.com"
        },
        {
          "propertyName": "PhoneNumber",
          "fieldKind": "Text",
          "isEncrypted": true,
          "hasBlindIndex": false,
          "replacement": "[REDACTED]"
        }
      ],
      "blindIndexes": [
        {
          "indexName": "idx_customer_email",
          "sourceProperty": "Email",
          "isCompound": false,
          "transforms": ["Lowercase", "Trim"]
        },
        {
          "indexName": "idx_customer_name",
          "sourceProperty": "FullName",
          "isCompound": false,
          "transforms": ["Lowercase"]
        }
      ],
      "protectedBy": ["EF Core"],
      "isFullyEncrypted": true
    }
  ],
  "summary": {
    "totalEntityTypes": 4,
    "totalPiiFields": 12,
    "encryptedFields": 12,
    "blindIndexedFields": 5,
    "fullyEncryptedEntities": 4,
    "partiallyEncryptedEntities": 0,
    "integrationCount": 1
  }
}

Markdown Export

Use ExportAsMarkdown() to generate a human-readable Markdown document suitable for Art. 30 documentation:

csharp
var markdown = _compliance.ExportInventoryAsMarkdown();
await File.WriteAllTextAsync("pii-data-map.md", markdown);

The Markdown output includes a summary table and detailed per-entity sections:

markdown
# PII Data Map -- MyApp

*Generated: 2026-03-10 14:30:00 UTC*

## Summary

| Metric | Value |
|--------|-------|
| Entity types with PII | 4 |
| Total PII fields | 12 |
| Encrypted fields | 12 |
| Blind-indexed fields | 5 |
| Fully encrypted entities | 4 |
| Active integrations | 1 |

## Entity Details

### Customer

**Full type:** `MyApp.Models.Customer`
**Protected by:** EF Core
**Encryption status:** Fully encrypted

**Data Subjects:**

| Property | Group | Prefix |
|----------|-------|--------|
| `CustomerId` | -- | cust- |

**PII Fields:**

| Field | Kind | Encrypted | Blind Index | Replacement |
|-------|------|-----------|-------------|-------------|
| `FullName` | Text | Yes | Yes | [REDACTED] |
| `Email` | Text | Yes | Yes | redacted@example.com |
| `PhoneNumber` | Text | Yes | No | [REDACTED] |

Integration Providers

Integration packages (EF Core, Marten, MassTransit, etc.) can implement IPiiIntegrationProvider to report which entity types they protect. This information appears in the ProtectedBy field of each entity entry.

csharp
public interface IPiiIntegrationProvider
{
    /// The integration name (e.g., "EF Core", "Marten", "MassTransit").
    string IntegrationName { get; }

    /// Returns the entity types protected by this integration.
    IReadOnlyList<Type> GetProtectedTypes();
}

Register your custom integration provider:

csharp
services.AddSingleton<IPiiIntegrationProvider, MyIntegrationProvider>();

When the inventory service generates a report, it queries all registered IPiiIntegrationProvider implementations and maps their protected types to entity entries. If an entity is protected by multiple integrations (e.g., encrypted by EF Core for storage and scrubbed by MassTransit for messaging), all integration names appear in the ProtectedBy list.

Use Cases

Hand to Your DPO or Auditor

Generate the Markdown report and include it in your compliance documentation package. The report is self-contained and requires no additional context to understand:

csharp
var markdown = compliance.ExportInventoryAsMarkdown();
await File.WriteAllTextAsync(
    $"art30-data-map-{DateTimeOffset.UtcNow:yyyy-MM-dd}.md",
    markdown);

CI/CD Encryption Coverage Gate

Run the inventory during your build pipeline to verify that all PII fields are encrypted. Fail the build if any entity is only partially encrypted:

csharp
var report = compliance.GenerateInventory();

if (report.Summary.PartiallyEncryptedEntities > 0)
{
    var partial = report.Entities
        .Where(e => !e.IsFullyEncrypted)
        .Select(e => e.EntityType);

    throw new InvalidOperationException(
        $"Partial encryption detected on: {string.Join(", ", partial)}");
}

CLI Assembly Scanning

Use the dotnet tayra inventory command to scan a compiled assembly without running the application. This performs reflection-based scanning of [PersonalData], [DataSubjectId], and [BlindIndex] attributes:

bash
dotnet tayra inventory --assembly bin/Release/net9.0/MyApp.dll

See the CLI Tool page for full command reference.

Automated Compliance Documentation

Generate the inventory on a schedule and commit it to your compliance repository:

csharp
public class WeeklyComplianceReportJob
{
    private readonly ITayraCompliance _compliance;

    public WeeklyComplianceReportJob(ITayraCompliance compliance)
    {
        _compliance = compliance;
    }

    public async Task ExecuteAsync()
    {
        var json = _compliance.ExportInventoryAsJson();
        await File.WriteAllTextAsync("compliance/pii-inventory.json", json);

        var markdown = _compliance.ExportInventoryAsMarkdown();
        await File.WriteAllTextAsync("compliance/pii-data-map.md", markdown);
    }
}

See Also

  • Compliance Reports -- Generate formatted Art. 30, Art. 15, breach, and coverage reports
  • GDPR Overview -- Full GDPR article mapping
  • CLI Tool -- Assembly scanning and report generation from the command line
  • Observability -- OpenTelemetry metrics and distributed tracing