Feature: Event System Domain Event Taxonomy As a platform operator I want a complete domain event taxonomy with typed event types and a reactive bus So that I can audit, observe, and react to all system events consistently # --------------------------------------------------------------------------- # EventType enum completeness # --------------------------------------------------------------------------- Scenario: EventType enum has at least 43 members Then the EventType enum should have at least 43 members Scenario: EventType covers the Plan lifecycle domain Then EventType should contain "plan.created" And EventType should contain "plan.phase_changed" And EventType should contain "plan.state_changed" And EventType should contain "plan.applied" And EventType should contain "plan.cancelled" And EventType should contain "plan.errored" Scenario: EventType covers the Decision domain Then EventType should contain "decision.created" And EventType should contain "decision.approved" And EventType should contain "decision.corrected" And EventType should contain "decision.superseded" Scenario: EventType covers the Invariant domain Then EventType should contain "invariant.reconciled" And EventType should contain "invariant.violated" And EventType should contain "invariant.enforced" Scenario: EventType covers the Actor domain Then EventType should contain "actor.invoked" And EventType should contain "actor.completed" And EventType should contain "actor.errored" And EventType should contain "actor.escalated" Scenario: EventType covers the Tool domain Then EventType should contain "tool.invoked" And EventType should contain "tool.completed" And EventType should contain "tool.errored" And EventType should contain "tool.retried" Scenario: EventType covers the Resource domain Then EventType should contain "resource.accessed" And EventType should contain "resource.modified" And EventType should contain "resource.indexed" Scenario: EventType covers the Correction domain Then EventType should contain "correction.applied" Scenario: EventType covers the Configuration domain Then EventType should contain "config.changed" Scenario: EventType covers the Entity lifecycle domain Then EventType should contain "entity.deleted" Scenario: EventType covers the Authentication domain Then EventType should contain "auth.success" And EventType should contain "auth.failure" Scenario: EventType covers the Sandbox domain Then EventType should contain "sandbox.created" And EventType should contain "sandbox.committed" And EventType should contain "sandbox.rolled_back" Scenario: EventType covers the Checkpoint domain Then EventType should contain "checkpoint.created" And EventType should contain "checkpoint.restored" Scenario: EventType covers the Context domain Then EventType should contain "context.built" And EventType should contain "context.query_executed" Scenario: EventType covers the Validation domain Then EventType should contain "validation.started" And EventType should contain "validation.passed" And EventType should contain "validation.failed" Scenario: EventType covers the Session domain Then EventType should contain "session.created" And EventType should contain "session.message_sent" Scenario: EventType covers the Budget domain Then EventType should contain "budget.warning" And EventType should contain "budget.exceeded" Scenario: EventType uses StrEnum with dot-separated values Then every EventType value should match the pattern "." # --------------------------------------------------------------------------- # DomainEvent Pydantic model # --------------------------------------------------------------------------- Scenario: DomainEvent has all required fields When I create a DomainEvent for taxonomy with event_type "plan.created" Then the taxonomy event should have field "event_type" And the taxonomy event should have field "timestamp" And the taxonomy event should have field "correlation_id" And the taxonomy event should have field "plan_id" And the taxonomy event should have field "root_plan_id" And the taxonomy event should have field "session_id" And the taxonomy event should have field "actor_name" And the taxonomy event should have field "project_name" And the taxonomy event should have field "user_identity" And the taxonomy event should have field "details" Scenario: DomainEvent correlation_id is a 26-char ULID string When I create a DomainEvent for taxonomy with event_type "plan.created" Then the taxonomy event correlation_id should be 26 characters long Scenario: DomainEvent timestamp is timezone-aware UTC When I create a DomainEvent for taxonomy with event_type "plan.created" Then the taxonomy event timestamp should be UTC Scenario: DomainEvent is frozen (immutable) When I create a DomainEvent for taxonomy with event_type "plan.created" Then mutating the taxonomy event should raise a validation error Scenario: DomainEvent serializes to JSON round-trip When I create a DomainEvent for taxonomy with all fields populated Then the taxonomy event should serialize to JSON and deserialize back identically Scenario: DomainEvent correlation fields accept ULID strings When I create a DomainEvent for taxonomy with correlation fields Then the taxonomy event plan_id should be set And the taxonomy event root_plan_id should be set And the taxonomy event session_id should be set Scenario: DomainEvent details dict preserves custom keys When I create a DomainEvent for taxonomy with typed details Then the taxonomy event details should contain the expected keys Scenario: DomainEvent user_identity defaults to None When I create a DomainEvent for taxonomy with event_type "plan.created" Then the taxonomy event user_identity should be None Scenario: DomainEvent accepts explicit user_identity When I create a DomainEvent for taxonomy with user_identity "alice@example.com" Then the taxonomy event user_identity should be "alice@example.com" Scenario: DomainEvent with user_identity serializes to JSON round-trip When I create a DomainEvent for taxonomy with user_identity "bob@example.com" Then the taxonomy event should serialize to JSON and deserialize back identically # --------------------------------------------------------------------------- # EventBus Protocol # --------------------------------------------------------------------------- Scenario: EventBus Protocol defines emit and subscribe Then the EventBus Protocol should define method "emit" And the EventBus Protocol should define method "subscribe" Scenario: ReactiveEventBus satisfies the EventBus Protocol Then a ReactiveEventBus instance should satisfy the EventBus Protocol Scenario: LoggingEventBus satisfies the EventBus Protocol Then a LoggingEventBus instance should satisfy the EventBus Protocol Scenario: LoggingEventBus emits to subscribed handlers Given a fresh LoggingEventBus for taxonomy tests When I logging-taxonomy-subscribe to "plan.created" events And I logging-taxonomy-emit a "plan.created" DomainEvent Then the logging taxonomy handler should have received 1 event(s) Scenario: LoggingEventBus continues dispatching when a handler raises Given a fresh LoggingEventBus for taxonomy tests When I logging-taxonomy-subscribe a failing handler and a normal handler to "plan.created" events And I logging-taxonomy-emit a "plan.created" DomainEvent Then the logging taxonomy normal handler should have received 1 event(s) Scenario: LoggingEventBus rejects non-DomainEvent on emit Given a fresh LoggingEventBus for taxonomy tests Then logging-taxonomy-emitting a non-DomainEvent should raise TypeError Scenario: LoggingEventBus rejects non-EventType on subscribe Given a fresh LoggingEventBus for taxonomy tests Then logging-taxonomy-subscribing with a non-EventType should raise TypeError Scenario: LoggingEventBus rejects non-callable handler on subscribe Given a fresh LoggingEventBus for taxonomy tests Then logging-taxonomy-subscribing with a non-callable should raise TypeError # --------------------------------------------------------------------------- # ReactiveEventBus — emit / subscribe / stream # --------------------------------------------------------------------------- Scenario: ReactiveEventBus emits to subscribed handlers Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-subscribe to "plan.created" events And I taxonomy-emit a "plan.created" DomainEvent Then the taxonomy handler should have received 1 event(s) Scenario: ReactiveEventBus does not dispatch to non-matching types Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-subscribe to "plan.created" events And I taxonomy-emit a "decision.created" DomainEvent Then the taxonomy handler should have received 0 event(s) Scenario: ReactiveEventBus supports multiple handlers per type Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-subscribe two handlers to "tool.invoked" events And I taxonomy-emit a "tool.invoked" DomainEvent Then each taxonomy handler should have received 1 event Scenario: ReactiveEventBus continues dispatching when a handler raises Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-subscribe a failing handler and a normal handler to "plan.created" events And I taxonomy-emit a "plan.created" DomainEvent Then the taxonomy normal handler should have received 1 event(s) And the audit_log should contain 1 events Scenario: ReactiveEventBus exposes an rx.Observable stream Given a fresh ReactiveEventBus for taxonomy tests Then the taxonomy bus stream should be an Observable Scenario: ReactiveEventBus stream is read-only Given a fresh ReactiveEventBus for taxonomy tests Then the taxonomy bus stream should not expose method "on_next" Scenario: ReactiveEventBus observable stream receives emitted events Given a fresh ReactiveEventBus for taxonomy tests When I observe the taxonomy bus stream and emit a "plan.created" event Then the stream observer should have captured the event Scenario: ReactiveEventBus continues when a stream observer raises Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-subscribe to "plan.created" events And I observe taxonomy stream with a failing observer and emit a "plan.created" event Then the taxonomy handler should have received 1 event(s) And the audit_log should contain 1 events Scenario: ReactiveEventBus rejects non-DomainEvent on emit Given a fresh ReactiveEventBus for taxonomy tests Then taxonomy-emitting a non-DomainEvent should raise TypeError Scenario: ReactiveEventBus rejects non-EventType on subscribe Given a fresh ReactiveEventBus for taxonomy tests Then taxonomy-subscribing with a non-EventType should raise TypeError Scenario: ReactiveEventBus rejects non-callable handler on subscribe Given a fresh ReactiveEventBus for taxonomy tests Then taxonomy-subscribing with a non-callable should raise TypeError # --------------------------------------------------------------------------- # In-memory event trail (audit_log) # --------------------------------------------------------------------------- Scenario: ReactiveEventBus audit_log captures all emitted events Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-emit a "plan.created" DomainEvent And I taxonomy-emit a "decision.created" DomainEvent Then the audit_log should contain 2 events And the audit_log should include event types "plan.created,decision.created" Scenario: ReactiveEventBus audit_log preserves event order Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-emit a "plan.created" DomainEvent And I taxonomy-emit a "decision.created" DomainEvent And I taxonomy-emit a "tool.invoked" DomainEvent Then the audit_log events should be in order "plan.created,decision.created,tool.invoked" Scenario: ReactiveEventBus audit_log returns a defensive copy Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-emit a "plan.created" DomainEvent Then mutating the returned audit_log should not affect the bus Scenario: ReactiveEventBus can cap in-memory audit_log retention Given a ReactiveEventBus for taxonomy tests with max_audit_log_size 2 When I taxonomy-emit a "plan.created" DomainEvent And I taxonomy-emit a "decision.created" DomainEvent And I taxonomy-emit a "tool.invoked" DomainEvent Then the audit_log should contain 2 events And the audit_log should include event types "decision.created,tool.invoked" Scenario: ReactiveEventBus clear_audit_log removes retained events Given a fresh ReactiveEventBus for taxonomy tests When I taxonomy-emit a "plan.created" DomainEvent And I clear the taxonomy bus audit_log Then the audit_log should contain 0 events Scenario: ReactiveEventBus rejects invalid max_audit_log_size Then creating a ReactiveEventBus with max_audit_log_size 0 should raise ValueError # --------------------------------------------------------------------------- # Log correlation # --------------------------------------------------------------------------- Scenario: Events sharing a correlation_id are traceable Given a fresh ReactiveEventBus for taxonomy tests When I emit two taxonomy events with the same correlation_id Then both events in the audit_log should share the same correlation_id Scenario: Independent DomainEvents get unique correlation_ids by default When I create two independent DomainEvents for taxonomy Then the two taxonomy events should have different correlation_ids