Chronicle.Concept behaviour (cratis_chronicle v3.0.0)

Copy Markdown View Source

Macro for defining Chronicle concepts — strongly-typed wrappers over a single primitive value, mirroring ConceptAs<T> in the C# client and ConceptAs in the Kotlin client.

A concept lets you declare a domain value's type — and, critically, its compliance classification — exactly once, on the type itself, instead of repeating a pii/1,2 annotation on every event or read model field that happens to hold that value.

Defining a concept

defmodule MyApp.PersonName do
  use Chronicle.Concept, type: :string
  pii "Legal name of the data subject"
end

Any struct field across your event types and read models that defaults to %MyApp.PersonName{} is now automatically recognized by Chronicle.Schemas.JsonSchemaGenerator as carrying PII — see that module's documentation for how type-level compliance is resolved and pushed down onto schema leaves.

Options for use Chronicle.Concept

  • :type(required) the primitive type the concept wraps. One of :string, :integer, :float, :boolean, or :uuid. Any other value raises ArgumentError at compile time.
  • :event_source_id — when true, marks this concept as representing a Chronicle event source identifier, mirroring EventSourceId<T> in the C# client. Defaults to false. A concept declared with event_source_id: true cannot also declare pii/0,1 — see "PII is not supported on an event source id" below.

What use Chronicle.Concept generates

  • defstruct value: <default> — a single-field struct wrapping the declared type's sensible zero value ("" for :string and :uuid, 0 for :integer, 0.0 for :float, false for :boolean).
  • __chronicle_concept__/1 — introspection, accepting :type, :event_source_id?, or :pii as the key.
  • A Jason.Encoder implementation that serializes the concept as its wrapped value, not as an object wrapping one — Jason.encode!(%MyApp.PersonName{value: "Ada"}) produces "Ada", exactly as Jason.encode!("Ada") would. This is critical: it is what lets an existing property already in production be adopted into a concept without changing the wire format or the registered schema, mirroring the intent of Kotlin's ConceptTypeAdapterFactory and the KDoc on io.cratis.chronicle.concepts.ConceptAs.
  • __chronicle_pii__/0 — present on every concept module (empty when no pii/0,1 was declared), so Chronicle.Schemas.JsonSchemaGenerator can treat every concept module uniformly, the same way it already treats Chronicle.Events.EventType and Chronicle.ReadModels.ReadModel modules.

Declaring PII on a concept

Use pii/0 or pii/1not Chronicle.Compliance.pii/1,2, which expects a field name as its first argument. A concept has exactly one field (:value), so its pii/0,1 takes only the optional details text:

defmodule MyApp.NationalId do
  use Chronicle.Concept, type: :string
  pii "National ID number — sensitive personal identifier"
end

PII is not supported on an event source id

Chronicle uses the event source id to look up the encryption key for a subject's PII values. A concept cannot be both the key used to find the encryption key and one of the values encrypted under it, so declaring pii/0,1 on a concept created with event_source_id: true raises ArgumentError at compile time — mirroring the C# client's PIINotSupportedOnEventSourceId:

defmodule MyApp.EmployeeId do
  use Chronicle.Concept, type: :uuid, event_source_id: true
  pii "this raises ArgumentError"
end

If the identifier itself is sensitive, use a non-sensitive surrogate (event_source_id: true, no pii) as the event source id, and store the sensitive value in a separate PII-marked concept or event property.

Registering a value

A concept is a plain struct — construct it like any other:

%MyApp.PersonName{value: "Ada Lovelace"}

Summary

Callbacks

Returns metadata for this concept module.

Functions

Marks this concept's wrapped value as containing Personally Identifiable Information (PII).

Callbacks

__chronicle_concept__(key)

@callback __chronicle_concept__(key :: :type | :event_source_id? | :pii) :: term()

Returns metadata for this concept module.

Accepts :type, :event_source_id?, or :pii as the key.

Functions

pii(details \\ "")

(macro)

Marks this concept's wrapped value as containing Personally Identifiable Information (PII).

details is an optional human-readable explanation of why the value is classified as PII and defaults to an empty string. Cannot be combined with event_source_id: true — see the module documentation.