Skip to content

ADR-008: Rejection Is Not an Event

Context

When a require guard fails in a decide clause, the system must handle the failure. Two schools of thought exist in Event Sourcing:

ApproachMechanismEvent stream impact
Emit a rejection eventCommandRejected eventStream records the attempt
Return an errorErr(RejectionError)Stream unchanged

Source: specs/theory.allium resolved question T-1; specs/core.allium Co-4 (RejectionError value type).

Decision

A rejection means nothing happened. decide returns Result<Events[], RejectionError>. On require failure: Err with rejection message. No events are produced and the event stream is left untouched.

The rationale: events represent domain facts — things that happened. A rejected command is not a fact about the domain; it is an operational result. Audit trails for failed attempts belong in a separate bounded context (Audit/Logging) that subscribes to command results — not the domain decider’s concern.

Consequences

Positive

  • Event stream contains only domain facts; replaying it always produces a correct state.
  • Decider logic stays pure — no side effects on rejection path.
  • Clear separation of concerns: domain integrity vs. audit/observability.
  • Generated TypeScript code expresses rejection naturally via Result without special cases.

Negative

  • Systems requiring attempt tracking must implement a separate audit bounded context.
  • It is not possible to reconstruct “what was tried” from the domain event stream alone; that information lives only in the audit context.