Skip to content

Add Policy automation: react to domain events with commands - #179

Merged
vanrogu merged 1 commit into
developfrom
claude/event-driven-automation-policy-wabyjz
Oct 4, 2026
Merged

vanrogu merged 1 commit into
developfrom
claude/event-driven-automation-policy-wabyjz

Conversation

@vanrogu

@vanrogu vanrogu commented Oct 4, 2026

Copy link
Copy Markdown
Member

Summary

Implements Policies, the second form of automation in Event Modeling: a simpler alternative to the to-do list pattern where a policy reacts directly to a domain event by issuing a command, without an intermediate read model.

A policy answers the event storming pattern "whenever X happens, then Y" — when a single event type directly triggers a single action with no need for a to-do list, deadline, or aggregation across multiple facts.

Key Changes

Core Policy Framework

  • Policy<DOMAIN_EVENT_TYPE> interface: defines eventQuery() to match triggering events and react(Event) to answer the command to issue
  • PolicyModule: runs one processor per registered policy, projecting the domain stream one event at a time on a single elected leader
  • Reaction: executes the policy's command with idempotency keys, tracing (correlation id + causation), and duplicate detection via trace lookup
  • PolicyStart and PolicyRejectionHandling enums: registration choices (where to start reading, how to handle business rejections)

Builder Integration

  • BoundedContextBuilder.policy(Policy) registration method with fluent API for configuration choices
  • PolicySpecification and PolicyRejectionSpecification for the two required registration decisions
  • Build-time validation: policies must specify both start position and rejection handling; policies must be registered in the AUTOMATION aspect

Observability & Management

  • PolicyStarted, PolicyFailed, PolicySkipped events in BoundedContextEvent to report processor lifecycle and stalls
  • ReactionResult outcome type for policy reactions (executed, already reacted, rejected)
  • Reaction observation scope for monitoring individual policy reactions
  • skipStalledEvent(EventId) in ProcessorAdminCapability to let operators skip a stalled policy event

Testing Support

  • PolicyTest<D,I,O> base class: fluent API to seed domain events, trigger reactions, and assert on resulting commands
  • Handles redelivery scenarios (crash between append and bookmark)
  • Supports both STALL and SKIP rejection handling modes
  • PolicyTestRunsOnEveryBackendTest: validates the test harness against all event storage backends

Supporting Changes

  • Tracing now includes causationId to name the event that caused a reaction
  • SliceType.derive() updated to recognize policies as AUTOMATION slices
  • ProjectorProcessor enhancements: retryRequested flag for operator skip, batchSize and startAtHeadWhenUnbookmarked configuration
  • Mock policy implementations for testing: SecondFromFirstPolicy, RecordSecondCommand

Implementation Details

  • One event at a time: policies process events individually (not in batches) so failures roll back only one event, avoiding redundant command re-executions on retry
  • Stall detection: the module remembers which event each policy is stalled on, enabling operator intervention via skip
  • Idempotency: reactions leave a trace (domain event tagged with policy as actor and triggering event as cause) that is checked before re-execution, making redelivery safe
  • Leader-only: all policy processors run on a single elected leader, like automations
  • Rejection handling: policies can be configured to either stall (retry) or skip when a command is rejected, a registration-time choice

Documentation

Updated CLAUDE.md with comprehensive policy documentation including the gap it fills (avoiding artificial to-do lists for simple event→command reactions), registration API, and lifecycle.

https://claude.ai/code/session_01CbFecBFD17EgzYnoszEgfX

A policy is the second form of an automation: it reacts to a triggering
domain event itself, with no to-do list in between, and maps it onto zero
or one command. Registration states where it starts (fromNowOn /
fromTheBeginning) and what a business rejection does (stallOnRejection /
skipRejections), both mandatory. A redelivered event is not reacted to
again: the reaction's trace (x-caused-by plus the policy as actor) is
looked up first, and the command runs under the key
policy:<name>@<event id>. A stalled policy can be moved past the event it
is stalled on by an operator (skipStalledEvent, SkipStalledEvent).

SliceType.derive gains a triggering-event count, shared with the modeler;
PolicyTest is the published test base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbFecBFD17EgzYnoszEgfX
@vanrogu
vanrogu merged commit c9008f6 into develop Oct 4, 2026
2 checks passed
@vanrogu
vanrogu deleted the claude/event-driven-automation-policy-wabyjz branch October 4, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants