Correlation, identity, and causation
Every event Chronicle appends carries more than its own data. Three pieces of context travel alongside it, scoped to the current call and captured automatically on every append:
- Correlation ID — ties together every event, command, and side effect that belong to the same logical operation (a single HTTP request, a message-bus handler invocation).
- Identity — records who caused the change: a user, a system, or a service.
- Causation — records why: the chain of operations, from a root action down to the specific command that triggered this event.
All three are set once at the start of an operation (a request, a background job) and flow into every event appended during it, without threading them through every function call.
Correlation ID
Section titled “Correlation ID”using Cratis.Execution;
public class CorrelationIdentityCausationCorrelation(ICorrelationIdAccessor accessor, ICorrelationIdModifier modifier){ public CorrelationId GetCurrent() => accessor.Current;
public void SetForRequest() => modifier.Modify(CorrelationId.New());}import io.cratis.chronicle.correlation.correlationIdManagerimport java.util.UUID
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationCorrelation { fun getCurrent(): UUID = correlationIdManager.current
fun setForRequest() = correlationIdManager.set(UUID.randomUUID())}import io.cratis.chronicle.correlation.CorrelationIdManagerKt;import java.util.UUID;
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationCorrelation { UUID getCurrent() { return CorrelationIdManagerKt.getCorrelationIdManager().getCurrent(); }
void setForRequest() { CorrelationIdManagerKt.getCorrelationIdManager().set(UUID.randomUUID()); }}defmodule MyApp.CorrelationIdentityCausationCorrelation do alias Chronicle.Correlation.CorrelationId
def get_current do Chronicle.current_correlation_id() end
def set_for_request do Chronicle.set_correlation_id(CorrelationId.create()) endendimport { correlationIdManager, CorrelationId } from '@cratis/chronicle';
class CorrelationIdentityCausationCorrelation { getCurrent(): CorrelationId { return correlationIdManager.current; }
setForRequest(): void { correlationIdManager.setCurrent(CorrelationId.create()); }}Kotlin’s correlation manager scopes the id per-thread (ThreadLocal); Elixir and TypeScript scope it per logical call context (Elixir’s process dictionary, TypeScript’s AsyncLocalStorage) — in both cases automatically, without extra code, as long as you don’t spawn new threads/processes/async contexts without carrying the value across explicitly.
Identity
Section titled “Identity”using Cratis.Chronicle.Identities;
public class CorrelationIdentityCausationIdentity(IIdentityProvider identityProvider){ public void SetForRequest(string subject, string name, string userName) => identityProvider.SetCurrentIdentity(new Identity(subject, name, userName));
public Identity GetCurrent() => identityProvider.GetCurrent();}import io.cratis.chronicle.identity.Identityimport io.cratis.chronicle.identity.identityProvider
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationIdentity { fun setForRequest(subject: String, name: String, userName: String) { identityProvider.setCurrentIdentity(Identity(subject, name, userName)) }
fun getCurrent(): Identity = identityProvider.currentIdentity}import io.cratis.chronicle.identity.Identity;import io.cratis.chronicle.identity.IdentityProviderKt;
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationIdentity { void setForRequest(String subject, String name, String userName) { IdentityProviderKt.getIdentityProvider().setCurrentIdentity(new Identity(subject, name, userName, null)); }
Identity getCurrent() { return IdentityProviderKt.getIdentityProvider().getCurrentIdentity(); }}defmodule MyApp.CorrelationIdentityCausationIdentity do alias Chronicle.Identity
def set_for_request(subject, name, user_name) do Chronicle.set_identity(Identity.new(subject, name, user_name)) end
def get_current do Chronicle.current_identity() endendimport { identityProvider, Identity } from '@cratis/chronicle';
class CorrelationIdentityCausationIdentity { setForRequest(subject: string, name: string, userName: string): void { identityProvider.setCurrentIdentity(new Identity(subject, name, userName)); }
getCurrent(): Identity { return identityProvider.getCurrent(); }}Every client provides the same three sentinel identities for well-known cases — System, NotSet, and Unknown — and supports on-behalf-of delegation chains (an identity that acted for another identity) via a withoutDuplicates()/without_duplicates/1 helper that collapses repeated subjects in a long chain.
Renaming an identity
Section titled “Renaming an identity”The name recorded alongside an identity is a display name, and people’s names change. Renaming updates it in one place for every event that identity has ever caused — the events themselves are untouched, since the name is not part of the fact they record.
using Cratis.Chronicle;
public static class RenamingAnIdentity{ public static async Task Rename(IEventStore eventStore) { await eventStore.Identities.Rename("subject-42", "Jane Austen"); }}import io.cratis.chronicle.IEventStore
class CorrelationIdentityCausationRenamingAnIdentity { suspend fun rename(eventStore: IEventStore) { eventStore.identities.rename("subject-42", "Jane Austen") }}import io.cratis.chronicle.IEventStore;
import io.cratis.chronicle.java.IdentityManagerServiceJavaBridge;
class CorrelationIdentityCausationRenamingAnIdentity { void rename(IEventStore eventStore) { IdentityManagerServiceJavaBridge.rename(eventStore.getIdentities(), "subject-42", "Jane Austen"); }}defmodule MyApp.CorrelationIdentityCausationRenamingAnIdentity do alias Chronicle.Identities
def rename do Identities.rename("subject-42", "Jane Austen") endendimport { IEventStore } from '@cratis/chronicle';
async function renameAnIdentity(eventStore: IEventStore): Promise<void> { await eventStore.identities.rename('subject-42', 'Jane Austen');}The subject is the identity’s stable key. It is what events are recorded against, so it does not change.
Identity resolution on metadata reads
Section titled “Identity resolution on metadata reads”Metadata reads resolve caused-by
identities directly from storage rather than a silo’s identity cache. A rename is visible on
the next metadata read, including for events appended before the rename. Each chain link has
a Resolution status:
| Status | Meaning | Name |
|---|---|---|
Resolved | The identity exists and has a display name. | The current name. |
NameUnavailable | The identity exists but its display name is empty. | null. |
Missing | The stored identity identifier no longer resolves. | null. |
NotSet | No caused-by identity was set. | null. |
System | The subject is Chronicle’s well-known system identity. | null. |
Chronicle never substitutes the subject for an unavailable name. OnBehalfOf retains the
chain order, resolving each link separately. The head’s InitiatorType is Agent when a
resolved identity acts on behalf of another identity, User for a resolved identity without
that chain, System for Chronicle, and Unknown for a missing or unset head. An unavailable
display name does not change whether that identity acts as an agent.
Application-defined service identities are not automatically classified as systems. Recognize
them by their subjects in your application, rather than relying on display names or the
User classification.
PII erasure removes encryption keys for protected event and read-model data; it does not erase identity display names. To make a name unavailable on metadata reads, rename that identity to an empty name. Its subject and username remain available. Your application owns authorization and must enforce it before calling the metadata API.
Causation
Section titled “Causation”using Cratis.Chronicle.Auditing;
public class CorrelationIdentityCausationCausation(ICausationManager causationManager){ public void RecordPlaceOrder(string orderId) => causationManager.Add( "MyApp.Commands.PlaceOrder", new Dictionary<string, string> { ["orderId"] = orderId });}import io.cratis.chronicle.auditing.CausationTypeimport io.cratis.chronicle.auditing.causationManager
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationCausation { fun recordPlaceOrder(orderId: String) { causationManager.add(CausationType("MyApp.Commands.PlaceOrder"), mapOf("orderId" to orderId)) }}import io.cratis.chronicle.java.CausationManagerJavaBridge;
import java.util.Map;
import static io.cratis.chronicle.auditing.CausationManagerKt.getCausationManager;
// Per thread, not per request or coroutine; clear in finally on thread-pooled paths.class CorrelationIdentityCausationCausation { void recordPlaceOrder(String orderId) { CausationManagerJavaBridge.add( getCausationManager(), "MyApp.Commands.PlaceOrder", Map.of("orderId", orderId)); }}defmodule MyApp.CorrelationIdentityCausationCausation do alias Chronicle.Auditing.CausationManager
def record_place_order(order_id) do CausationManager.add("MyApp.Commands.PlaceOrder", %{order_id: order_id}) endendimport { causationManager, CausationType } from '@cratis/chronicle';
class CorrelationIdentityCausationCausation { recordPlaceOrder(orderId: string): void { causationManager.add(new CausationType('MyApp.Commands.PlaceOrder'), { orderId }); }}TypeScript also has a real causationManager.add(type, properties) — the client-specific pages linked below cover it, along with more Kotlin/Elixir/TypeScript causation detail than fits here (defining/redefining the root cause, reading the full chain, framework-integration examples for scoping context per request).
Client-specific reference
Section titled “Client-specific reference”Each client’s own documentation covers the full API in depth — provider interfaces, framework-integration examples (Express middleware, and similar), and thread/process/async-context caveats specific to that runtime:
- .NET client —
Cratis.Execution.ICorrelationIdAccessor/ICorrelationIdModifier,Cratis.Chronicle.Identities.IIdentityProvider,Cratis.Chronicle.Auditing.ICausationManager - Kotlin client —
io.cratis.chronicle.correlation,io.cratis.chronicle.identity,io.cratis.chronicle.auditing - Elixir client —
Chronicle.CorrelationId,Chronicle.Identity,Chronicle.Auditing.CausationManager - TypeScript client —
correlationIdManager,identityProvider,causationManager