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
Section titled “Attribute definition”The signature, for reference:
// C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter)]public sealed class PIIAttribute(string details = "") : Attribute
// TypeScriptexport function pii(details?: string): PropertyDecorator & ClassDecoratorThe 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);import { pii } from '@cratis/chronicle';import { ConceptAs } from '@cratis/fundamentals';
@pii('Collected under GDPR Art. 6(1)(b) — necessary for contract performance')class PiiAttrPersonName extends ConceptAs<string> { constructor(value: string) { super(value); }}Where the attribute is valid
Section titled “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
Section titled “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.
using Cratis.Chronicle.Compliance.GDPR;using Cratis.Chronicle.Events;
[EventType]public record PiiAttrEmployeeRegistered( [PII] string FirstName, [PII] string LastName, string Department);import io.cratis.chronicle.compliance.Piiimport io.cratis.chronicle.events.EventType
@EventType(id = "pii-attr-employee-registered")data class PiiAttrEmployeeRegistered( @Pii val firstName: String, @Pii val lastName: String, val department: String)Java does not support this workflow yet.defmodule MyApp.Events.PiiAttrEmployeeRegistered do use Chronicle.Events.EventType, id: "pii-attr-employee-registered"
defstruct [:first_name, :last_name, :department]
pii(:first_name) pii(:last_name)endimport { eventType, pii } from '@cratis/chronicle';
@eventType()class PiiAttrEmployeeRegistered { @pii() firstName = ''; @pii() lastName = ''; department = '';}When this event is written, FirstName and LastName are encrypted. Department is stored as plaintext.
Applying to a ConceptAs type
Section titled “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.
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);}import { pii } from '@cratis/chronicle';import { ConceptAs } from '@cratis/fundamentals';
@pii()class PiiAttrConceptPersonName extends ConceptAs<string> { constructor(value: string) { super(value); }}Constraints
Section titled “Constraints”EventSourceId is not supported
Section titled “EventSourceId is not supported”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.
Details parameter
Section titled “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.
using Cratis.Chronicle.Compliance.GDPR;
[PII("Full legal name — required for contract identification")]public record PiiAttrLegalName(string Value) : ConceptAs<string>(Value);import { pii } from '@cratis/chronicle';import { ConceptAs } from '@cratis/fundamentals';
@pii('Full legal name — required for contract identification')class PiiAttrLegalName extends ConceptAs<string> { constructor(value: string) { super(value); }}