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;
Attribute definition
[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);
Where the attribute is valid
| Target | Supported | Notes |
|---|---|---|
ConceptAs<T> class |
Yes | Preferred approach — marks the concept type itself as PII |
| Event property | Yes | Marks a single property of an event as PII |
EventSourceId or EventSourceId<T> |
No | Throws PIINotSupportedOnEventSourceId at runtime |
| Any other class type | No | Throws PIIAppliedToNonConceptAsType at runtime |
Applying to an event property
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.
Applying to a ConceptAs type
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);
}
Tip
Use Concepts as a reference for the full ConceptAs<T> pattern. For guidance on declaring compliance on concept types, see Applying PII to ConceptAs types.
Constraints
EventSourceId is not supported
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
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.
Details parameter
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);