Skip to content

PII Attribute

The [PII] attribute marks data as personally identifiable information (PII) under GDPR. When Chronicle sees this attribute on an event property or a ConceptAs<T> type, it encrypts the value automatically when the event is written to the event log and decrypts it transparently on read.

using Cratis.Chronicle.Compliance.GDPR;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class PIIAttribute(string details = "") : Attribute

The optional details parameter lets you record why the value is classified as PII — for example, the legal basis under which it is collected or the retention period. This information is stored in the event schema and can be used by compliance reporting tools.

[PII("Collected under GDPR Art. 6(1)(b) — necessary for contract performance")]
public record PersonName(string Value) : ConceptAs<string>(Value);
TargetSupportedNotes
ConceptAs<T> classYesPreferred approach — marks the concept type itself as PII
Event propertyYesMarks a single property of an event as PII
EventSourceId or EventSourceId<T>NoThrows PIINotSupportedOnEventSourceId at runtime
Any other class typeNoThrows PIIAppliedToNonConceptAsType at runtime

You can mark a single property on an event record as PII. This is useful when the property type is a primitive and you cannot or do not want to introduce a dedicated concept type.

[EventType]
public record EmployeeRegistered(
[PII] string FirstName,
[PII] string LastName,
string Department);

When this event is written, FirstName and LastName are encrypted. Department is stored as plaintext.

The preferred approach is to mark the ConceptAs<T> type itself as PII. Every property across every event that uses this type is then automatically encrypted — you declare the rule once and it applies everywhere.

[PII]
public record PersonName(string Value) : ConceptAs<string>(Value)
{
public static readonly PersonName NotSet = new(string.Empty);
public static implicit operator string(PersonName name) => name.Value;
public static implicit operator PersonName(string value) => new(value);
}

Applying [PII] to a type that inherits from EventSourceId or EventSourceId<T> throws PIINotSupportedOnEventSourceId at runtime:

// ❌ This will throw PIINotSupportedOnEventSourceId
[PII]
public record EmployeeId(Guid Value) : EventSourceId<Guid>(Value);

Event source identifiers are used to look up encryption keys and group events. Encrypting them would make key lookup impossible. If the identifier itself is sensitive, use a non-sensitive surrogate (such as a random Guid) as the event source identifier and store the sensitive value in a [PII]-marked event property.

Non-ConceptAs class types are not supported

Section titled “Non-ConceptAs class types are not supported”

Applying [PII] to a class that does not inherit from ConceptAs<T> throws PIIAppliedToNonConceptAsType at runtime:

// ❌ This will throw PIIAppliedToNonConceptAsType
[PII]
public class SomeArbitraryClass { }

The [PII] attribute at the class level is reserved for ConceptAs<T> types. Use a property-level [PII] annotation instead if you need to mark individual fields on a class.

The details parameter is a free-text description stored in the event schema. It is never used for encryption — it exists solely to record why a value is classified as PII for compliance documentation and auditing purposes.

[PII("Full legal name — required for contract identification")]
public record LegalName(string Value) : ConceptAs<string>(Value);