Chronicle event modeling
Use this before writing code, when behavior, event vocabulary, stream
boundaries, or a multi-step flow is not already settled. The output is an
implementation brief: which commands exist, which stream each event lands on,
which read models consume those events, which automations and translations react,
and which specifications prove the flow.
Skip it for a mechanical change where the event types and the flow already exist
and the request is wiring or a narrow fix.
Lineage. The four behavior types and the given/when/then-per-behavior
discipline follow Event Modeling (Adam Dymitruk; Martin Dilger,
Understanding Eventsourcing). This skill applies that method to Chronicle.
Verified product sources
This skill is verified against these exact sources:
| Package |
Version |
Purpose |
Cratis.Chronicle |
16.45.2 |
event types, event-source identities, subjects, [PII] |
Cratis.Chronicle.CodeAnalysis |
16.45.2 |
CHR0012, CHR0026, CHR0034, CHR0035 |
Reverify product sources before claiming support for another version.
The four behavior types
Classify every behavior in the model as exactly one:
| Type |
What it does |
Built from |
| State change |
accepts a command, appends events |
command, validation, events |
| State view |
projects events into a queryable read model |
read model plus a projection or a reducer |
| Automation |
reacts to events and causes an external side effect |
a reactor |
| Translation |
reacts to events and appends follow-up events elsewhere |
a reactor |
Automation and translation differ in the result: an automation reaches out of
the system, a translation stays inside it and produces new facts.
Decide before implementing
- Behavior name and type for each step in the flow.
- Commands — their inputs and the authorization that gates them. Commands
are imperative intents.
- Events — past-tense, one-purpose facts. Names must be self-describing:
<Entity><PastTenseVerb>, never a bare Created.
- The event-source id for every event. Events never carry their own
event-source id as a payload property; it lives in the event context. Deciding
the id is deciding the stream boundary.
- Event properties are non-nullable. Analyzer
CHR0012 warns otherwise.
Model an optional fact as a separate event, not a nullable field.
- Read models — their consumers and their source events, and whether the
model is projection-backed, reducer-backed, or
[Passive] for a
strongly-consistent command-side decision.
- Automations and translations — which events they react to, whether they
emit follow-up events or run commands, and which side effects must not repeat.
- Specifications — the happy path, the validation failures, the constraints,
the projections and reducers, and the reactor side effects.
Do not append an event for derived or aggregate state. Project it from the source
events instead; an event that records a total is a fact that can disagree with
its own inputs.
Information completeness — trace every field to an event
This is the check that pays for itself, and it belongs at modeling time rather
than after the projection misbehaves.
- Backward: for each read model, walk every property back to the event that
carries it. A field with no source event is a missing event or command —
not a nullable column. Resolve it in the model.
- Forward: every event you define should feed at least one read model,
automation, or translation. An event nothing consumes is a smell: either a
consumer is missing or the event should not exist.
If a field can only be filled by reaching into another behavior's read model,
you have found a missing event or a wrong stream boundary. Fix the model; do not
cross-read at runtime.
Compliance modeling
Decide this before choosing event and read-model shapes, because erasure
operates on the subject and the subject follows the stream.
- Prefer one subject per event stream for person-level personal data. The
subject then defaults correctly to the event-source id and needs no attribute.
- Mark inherently personal values with
[PII] — names, email addresses, phone
numbers, identity-provider subjects, personal notes. Leave business metadata
unmarked; every marked value becomes unreadable once the subject is erased.
- Record the rationale with
[ComplianceDetails("...")]. A string argument
passed to [PII] is not read by Chronicle.
- Set
[Subject] only when the person is not the event source. A stored
read-model document carries one subject — do not mix several people's personal
data into one document.
- Never place
[PII] on an event-source id: Chronicle cannot encrypt it, and
analyzer CHR0034 rejects it. When the natural identifier is itself
sensitive, model a surrogate stream id and carry the sensitive value as a
[PII] property.
- Do not place
[Key] or [Subject] on an EventSourceId<T> value — it is
already both, and analyzer CHR0026 says so.
- Bearer tokens, magic links, and signed URLs are not durable facts. Model a
keyed hash or an opaque reference, never the secret. Chronicle has no attribute
that withholds a value from the log.
If a subject boundary cannot be made person-level without changing product
behavior, stop and surface that trade-off before implementing.
Output shape
Write the brief in this order:
- stream boundaries and subjects
- commands and events
- read models and their consumers
- automations and translations
- compliance notes
- specifications
The lifecycle: draft, ready, working, done
The behavior is the unit of work, and it moves through four states. The model —
events, commands, read models, screens, specifications — lives inside it, and
anyone may author or update it.
- Draft — being modeled; events, commands, read models, and boundaries are
still in flux.
- Ready — the handoff gate. The model is information-complete: the checks
above pass, and commands, authorization, compliance, and the specification
outline are all decided. A ready behavior can be implemented with no further
modeling decisions.
- Working — an implementer has picked it up and is running the
implementation workflow end to end.
- Done — every quality gate is green.
Marking a behavior ready is the signal to implement it. Do not batch ready
items behind one another. Branching, pull requests, merging, and publication
remain separately authorized; a ready model authorizes none of them.
Verify the brief before handing it over
- Every behavior has exactly one type.
- Every event is past-tense, self-describing, single-purpose, and non-nullable.
- Every event has a decided event-source id, and none carries that id as a
property.
- Every read-model field traces back to an event.
- Every event has at least one consumer.
- No behavior depends on reading another behavior's read model at runtime.
- Personal data has a decided subject, and the subject is person-level or the
trade-off is surfaced.
- The specification outline names the failures, not only the happy path.
1---2name: cratis-chronicle-event-modeling3description: Design a Chronicle event model before writing code - stream boundaries and event-source identities, commands, past-tense events, read models, automations and translations, compliance subjects, and the specification outline. Use when behavior, event vocabulary, stream boundaries, or a multi-slice flow is not yet settled. Do not use to draw or update an existing model diagram, and do not use for a mechanical change to a flow that is already decided.4license: MIT5---67# Chronicle event modeling89Use this **before writing code**, when behavior, event vocabulary, stream10boundaries, or a multi-step flow is not already settled. The output is an11implementation brief: which commands exist, which stream each event lands on,12which read models consume those events, which automations and translations react,13and which specifications prove the flow.1415Skip it for a mechanical change where the event types and the flow already exist16and the request is wiring or a narrow fix.1718> **Lineage.** The four behavior types and the given/when/then-per-behavior19> discipline follow **Event Modeling** (Adam Dymitruk; Martin Dilger,20> *Understanding Eventsourcing*). This skill applies that method to Chronicle.2122## Verified product sources2324This skill is verified against these exact sources:2526| Package | Version | Purpose |27| --- | --- | --- |28| `Cratis.Chronicle` | `16.45.2` | event types, event-source identities, subjects, `[PII]` |29| `Cratis.Chronicle.CodeAnalysis` | `16.45.2` | `CHR0012`, `CHR0026`, `CHR0034`, `CHR0035` |3031Reverify product sources before claiming support for another version.3233## The four behavior types3435Classify every behavior in the model as exactly one:3637| Type | What it does | Built from |38| --- | --- | --- |39| **State change** | accepts a command, appends events | command, validation, events |40| **State view** | projects events into a queryable read model | read model plus a projection or a reducer |41| **Automation** | reacts to events and causes an external side effect | a reactor |42| **Translation** | reacts to events and appends follow-up events elsewhere | a reactor |4344Automation and translation differ in the *result*: an automation reaches out of45the system, a translation stays inside it and produces new facts.4647## Decide before implementing4849- **Behavior name and type** for each step in the flow.50- **Commands** — their inputs and the authorization that gates them. Commands51 are imperative intents.52- **Events** — past-tense, one-purpose facts. Names must be self-describing:53 `<Entity><PastTenseVerb>`, never a bare `Created`.54- **The event-source id for every event.** Events never carry their own55 event-source id as a payload property; it lives in the event context. Deciding56 the id *is* deciding the stream boundary.57- **Event properties are non-nullable.** Analyzer `CHR0012` warns otherwise.58 Model an optional fact as a separate event, not a nullable field.59- **Read models** — their consumers and their source events, and whether the60 model is projection-backed, reducer-backed, or `[Passive]` for a61 strongly-consistent command-side decision.62- **Automations and translations** — which events they react to, whether they63 emit follow-up events or run commands, and which side effects must not repeat.64- **Specifications** — the happy path, the validation failures, the constraints,65 the projections and reducers, and the reactor side effects.6667Do not append an event for derived or aggregate state. Project it from the source68events instead; an event that records a total is a fact that can disagree with69its own inputs.7071## Information completeness — trace every field to an event7273This is the check that pays for itself, and it belongs at modeling time rather74than after the projection misbehaves.7576- **Backward:** for each read model, walk every property back to the event that77 carries it. A field with no source event is a **missing event or command** —78 not a nullable column. Resolve it in the model.79- **Forward:** every event you define should feed at least one read model,80 automation, or translation. An event nothing consumes is a smell: either a81 consumer is missing or the event should not exist.8283If a field can only be filled by reaching into another behavior's read model,84you have found a missing event or a wrong stream boundary. Fix the model; do not85cross-read at runtime.8687## Compliance modeling8889Decide this *before* choosing event and read-model shapes, because erasure90operates on the subject and the subject follows the stream.9192- Prefer **one subject per event stream** for person-level personal data. The93 subject then defaults correctly to the event-source id and needs no attribute.94- Mark inherently personal values with `[PII]` — names, email addresses, phone95 numbers, identity-provider subjects, personal notes. Leave business metadata96 unmarked; every marked value becomes unreadable once the subject is erased.97- Record the rationale with `[ComplianceDetails("...")]`. A string argument98 passed to `[PII]` is **not** read by Chronicle.99- Set `[Subject]` only when the person is not the event source. A stored100 read-model document carries one subject — do not mix several people's personal101 data into one document.102- Never place `[PII]` on an event-source id: Chronicle cannot encrypt it, and103 analyzer `CHR0034` rejects it. When the natural identifier is itself104 sensitive, model a surrogate stream id and carry the sensitive value as a105 `[PII]` property.106- Do not place `[Key]` or `[Subject]` on an `EventSourceId<T>` value — it is107 already both, and analyzer `CHR0026` says so.108- Bearer tokens, magic links, and signed URLs are not durable facts. Model a109 keyed hash or an opaque reference, never the secret. Chronicle has no attribute110 that withholds a value from the log.111112If a subject boundary cannot be made person-level without changing product113behavior, **stop and surface that trade-off** before implementing.114115## Output shape116117Write the brief in this order:1181191. stream boundaries and subjects1202. commands and events1213. read models and their consumers1224. automations and translations1235. compliance notes1246. specifications125126## The lifecycle: draft, ready, working, done127128The behavior is the unit of work, and it moves through four states. The model —129events, commands, read models, screens, specifications — lives inside it, and130anyone may author or update it.131132- **Draft** — being modeled; events, commands, read models, and boundaries are133 still in flux.134- **Ready** — the handoff gate. The model is *information-complete*: the checks135 above pass, and commands, authorization, compliance, and the specification136 outline are all decided. A ready behavior can be implemented with **no further137 modeling decisions**.138- **Working** — an implementer has picked it up and is running the139 implementation workflow end to end.140- **Done** — every quality gate is green.141142**Marking a behavior ready is the signal to implement it.** Do not batch ready143items behind one another. Branching, pull requests, merging, and publication144remain separately authorized; a ready model authorizes none of them.145146## Verify the brief before handing it over147148- Every behavior has exactly one type.149- Every event is past-tense, self-describing, single-purpose, and non-nullable.150- Every event has a decided event-source id, and none carries that id as a151 property.152- Every read-model field traces back to an event.153- Every event has at least one consumer.154- No behavior depends on reading another behavior's read model at runtime.155- Personal data has a decided subject, and the subject is person-level or the156 trade-off is surfaced.157- The specification outline names the failures, not only the happy path.