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;

The signature, for reference:

// C#
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class PIIAttribute(string details = "") : Attribute
// TypeScript
export function pii(details?: string): PropertyDecorator & ClassDecorator

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.

using Cratis.Chronicle.Compliance.GDPR;
[PII("Collected under GDPR Art. 6(1)(b) — necessary for contract performance")]
public record PiiAttrPersonName(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.

using Cratis.Chronicle.Compliance.GDPR;
using Cratis.Chronicle.Events;
[EventType]
public record PiiAttrEmployeeRegistered(
[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.

using Cratis.Chronicle.Compliance.GDPR;
[PII]
public record PiiAttrConceptPersonName(string Value) : ConceptAs<string>(Value)
{
public static readonly PiiAttrConceptPersonName NotSet = new(string.Empty);
public static implicit operator string(PiiAttrConceptPersonName name) => name.Value;
public static implicit operator PiiAttrConceptPersonName(string value) => new(value);
}

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

using Cratis.Chronicle.Compliance.GDPR;
using Cratis.Chronicle.Events;
// ❌ This will throw PIINotSupportedOnEventSourceId
[PII]
public record PiiAttrEmployeeId(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:

using Cratis.Chronicle.Compliance.GDPR;
// ❌ This will throw PIIAppliedToNonConceptAsType
[PII]
public class PiiAttrSomeArbitraryClass
{
}

This restriction is .NET-client-specific — the TypeScript @pii() decorator does not check that its target extends ConceptAs<T>.

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.

using Cratis.Chronicle.Compliance.GDPR;
[PII("Full legal name — required for contract identification")]
public record PiiAttrLegalName(string Value) : ConceptAs<string>(Value);