Data Integrity exit conditions
Candidate exit conditions for the Data Integrity type. See the catalog overview for how to adopt these.
Entity state invariants
| Exit condition | What it checks |
|---|---|
|
An entity field constrained to be non-negative is rejected by the command handler whenever a transition would drive it below zero. |
|
Every state transition on the entity is one of the explicitly allowed edges, and disallowed transitions are refused rather than applied. |
|
An entity cannot reach an active lifecycle state while any field declared mandatory for that state is null or empty. |
|
Monetary amounts persist with the declared currency scale and reject values with excess decimal precision. |
|
A quantity field stays within its declared minimum and maximum bounds across every command that modifies it. |
|
Fields declared mutually exclusive are never simultaneously populated in any persisted entity state. |
|
A stored derived field (such as a running total) always equals the recomputation from its source fields after every state change. |
|
Once an entity enters a terminal state, subsequent state-mutating commands are rejected and leave the state unchanged. |
|
Collections held within an entity reject insertion of an element whose key already exists in the collection. |
|
An entity’s last-updated timestamp never moves backward across successive persisted state changes. |
Event sourcing correctness
| Exit condition | What it checks |
|---|---|
|
Applying an event-sourced entity’s full event log through its event handler reproduces exactly the current in-memory state. |
|
Every event type the entity can emit has a corresponding event handler, so no persisted event fails to apply on replay. |
|
A command handler mutates state only by emitting events, never by writing state directly outside the event application path. |
|
Replaying the same event sequence twice yields byte-identical entity state with no dependence on wall-clock time or randomness. |
|
Event handlers perform no side effects and no external calls, so replay produces state without emitting new commands or messages. |
|
A rejected command persists zero events, leaving the event log unchanged. |
|
Events are replayed in their original persisted sequence order, and reordering them is never required for correct state. |
|
An entity with no persisted events resolves to the declared empty initial state rather than null or an error. |
|
Persisted events are never mutated or deleted in place; corrections are expressed as new compensating events. |
|
Persisted event sequence numbers for an entity are contiguous with no gaps or duplicates. |
Schema evolution and upcasting
| Exit condition | What it checks |
|---|---|
|
Events serialized under a prior schema version still deserialize successfully after the current schema change. |
|
Every historical event schema version has an upcasting path to the current version, with no version left unmapped. |
|
A newly added event or state field deserializes to its declared default value when reading records written before the field existed. |
|
A field removed from the schema is ignored on deserialization of old records rather than causing a parse failure. |
|
A renamed field is mapped from its former name during deserialization so historical records retain their value. |
|
Deserializing an enum value absent from the current definition resolves to a defined fallback rather than throwing. |
|
A field’s serialized type is not changed incompatibly (such as string to integer) without an upcaster bridging the old form. |
|
Every serialized event carries a schema or type version marker enabling the correct upcaster to be selected on read. |
|
Serializing then deserializing a current-version record yields a value equal to the original with no field loss. |
|
A proposed event schema passes the registry’s backward-compatibility check before it is allowed to be deployed. |
Concurrency and versioning
| Exit condition | What it checks |
|---|---|
|
A write carrying a stale expected version is rejected rather than overwriting a concurrent update. |
|
The entity’s version or revision number increases by exactly one on each successfully persisted change. |
|
Two concurrent updates against the same initial version result in one success and one rejection, never a silently merged loss. |
|
All mutations for a given entity key are serialized through a single writer so no two writes to that key interleave. |
|
A compare-and-set update applies only when the stored value matches the supplied precondition and otherwise fails without writing. |
|
Retrying a persisted command with the same idempotency key produces no duplicate state change or duplicate event. |
|
An update request whose supplied entity tag no longer matches the current stored tag is rejected with a conflict. |
|
A read-modify-write cycle detects that the underlying record changed since it was read and aborts rather than clobbering it. |
|
Duplicate commands arriving within the configured deduplication window are collapsed to a single applied effect. |
Referential integrity
| Exit condition | What it checks |
|---|---|
|
A reference field is accepted only when the entity it points to currently exists. |
|
Deleting a referenced entity is blocked, or cascades, so no surviving record retains a dangling reference to it. |
|
A cascading delete removes every dependent record, leaving no orphaned children of the deleted parent. |
|
When a relationship is stored on both sides, both directions agree and neither points at a partner that omits the back-reference. |
|
A stored foreign identifier conforms to the referenced entity’s declared key format before it is persisted. |
|
A self-referencing hierarchy rejects an edge that would introduce a cycle in the parent chain. |
|
A referential-integrity scan over the data store finds zero records whose mandatory references resolve to missing targets. |
|
A cached count of related children on a parent equals the actual number of existing child records. |
|
References resolve only to records not marked soft-deleted, treating soft-deleted targets as absent. |
Persistence and transactional consistency
| Exit condition | What it checks |
|---|---|
|
A multi-write operation either commits all of its writes or none, never leaving a partial result. |
|
A command is acknowledged as successful only after its state change is durably persisted. |
|
A read issued after a successful write against the same entity observes that write. |
|
A transactional consistency boundary spans only one entity, and cross-entity coordination uses events rather than a shared transaction. |
|
A rejected item within a batch write does not leave earlier items in that batch persisted. |
|
After a simulated crash mid-operation, recovery restores the entity to a valid committed state with no half-applied change. |
|
State change and its outbound message are persisted together so the message is neither lost nor emitted without the state change. |
|
A read never observes state from an operation that was subsequently rolled back or rejected. |
|
A write that would exceed the entity’s configured storage or event-count limit is rejected rather than silently truncated. |
Snapshots and state recovery
| Exit condition | What it checks |
|---|---|
|
State restored from a snapshot plus subsequent events equals state obtained by replaying the full event log from the start. |
|
A snapshot written under a prior state schema version still restores correctly after the schema has evolved. |
|
A snapshot is produced at the configured event-count interval so replay length stays bounded. |
|
An unreadable snapshot causes recovery to fall back to full event replay rather than failing entity load. |
|
A snapshot records the exact event sequence number it represents so only strictly later events are applied on top of it. |
|
Producing or restoring a snapshot emits no events, commands, or external calls. |
|
Snapshots older than the retention policy are pruned without removing any snapshot still required for recovery. |
Views and projection consistency
| Exit condition | What it checks |
|---|---|
|
A view reflects a source entity change within the declared convergence bound after the change is persisted. |
|
Reprocessing the same source event into a view produces the same view row without double-counting. |
|
A projection tolerates receiving source events out of order and converges to the correct row regardless of arrival order. |
|
Each view row’s fields equal the projection of the current source entity state it derives from. |
|
Deleting a source entity removes or tombstones its corresponding view rows. |
|
A projection durably records its processed offset so a restart resumes without skipping or reprocessing consumed events. |
|
A view row is written with all of its projected fields populated, never a partially projected intermediate. |
|
Rebuilding a view from the source event history reproduces the same rows as the incrementally maintained view. |
|
A view’s grouping or partition key is derived deterministically so a source event always maps to the same view row. |
Workflow durable state
| Exit condition | What it checks |
|---|---|
|
An in-flight workflow resumes from its last durably persisted step after a process restart rather than restarting from the beginning. |
|
Re-executing a workflow step after a mid-step failure does not duplicate its effect on durable state. |
|
A workflow that completes or fails leaves no step stuck permanently in a pending state. |
|
A compensating step for a failed workflow returns affected durable state to a consistent pre-transaction condition. |
|
A workflow’s scheduled timeout is durably recorded so it still fires after a restart that spans the deadline. |
|
Each workflow step transition is persisted before the next step begins, so recovery never loses a completed step. |
|
A workflow in a completed or failed terminal state is not re-entered by a late-arriving step signal. |
Data validation and PII handling
| Exit condition | What it checks |
|---|---|
|
Incoming command payloads are validated for required fields at the entity boundary before any event is emitted. |
|
String fields with declared format constraints (email, UUID, ISO date) reject values that do not match the format. |
|
Numeric and date fields reject values outside their declared valid range before persistence. |
|
A payload containing fields not defined in the command schema is rejected or safely ignored rather than silently persisted. |
|
Text fields are normalized to a canonical encoding form before storage so equivalent inputs compare and deduplicate correctly. |
|
Fields classified as PII are stored encrypted rather than in plaintext at the persistence layer. |
|
Sensitive personal data is excluded from event payloads, or tokenized, so it is not retained in the immutable event log. |
|
Persisted diagnostic and audit records redact PII fields rather than recording their raw values. |
|
An erasure request removes or crypto-shreds all copies of a subject’s PII across entity state, snapshots, and views. |
|
A supplied idempotency key conforms to its required format before it is trusted for deduplication. |
Data migration and backfill correctness
| Exit condition | What it checks |
|---|---|
|
A migration preserves the source record count in the target, with any intentional drops explicitly accounted for. |
|
Migrated records match their source by field-level checksum for every value expected to be carried over unchanged. |
|
Re-running a migration over already-migrated data produces no duplicate or altered target records. |
|
A migration interrupted partway can resume from its last recorded checkpoint without reprocessing completed records. |
|
A backfill populates a newly required field for all existing records so none remain missing the field after completion. |
|
A failed migration can be rolled back to leave the source data in its original pre-migration state. |
|
During a dual-write cutover, records written to both old and new stores stay consistent with no divergence. |
|
Type conversions performed during migration neither truncate nor overflow any source value. |
|
A post-migration validation pass confirms every migrated record satisfies the target schema’s invariants. |