Event sourcing core — commands, events, decider & projector #4

Closed
opened 2026-04-16 11:28:21 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a backend developer, I want pure-function implementations of the Decider (command validation → events) and Projector (event folding → read model), so that the core business logic is fully testable without I/O and serves as the foundation for the OrchestrationEngine.

Acceptance criteria

Commands (orchestration/commands.rs)

  • OrchestrationCommand enum with all variants from spec §5.1:
    • Project: CreateProject, DeleteProject, RenameProject
    • Thread: CreateThread, DeleteThread, SelectThread
    • Turn: StartTurn, InterruptTurn, ApprovePendingAction, RejectPendingAction
    • Provider: IngestProviderEvent
    • Checkpoint: CreateCheckpoint, RestoreCheckpoint
    • Git: CommitChanges, CreateBranch, PushBranch

Events (orchestration/events.rs)

  • OrchestrationEvent enum with all variants from spec §5.2 (ProjectCreated, ThreadCreated, TurnStarted, MessageAppended, MessageChunkReceived, CheckpointCreated, etc.)
  • StoredEvent wrapper with sequence: u64, event: OrchestrationEvent, stored_at: DateTime<Utc>
  • ProviderEvent enum (TextDelta, ToolCall, ToolResult, ThinkingDelta, TurnComplete, Error, AwaitingApproval) as per spec §7.1

Decider (orchestration/decider.rs)

  • fn decide(command, model) -> Result<Vec<OrchestrationEvent>, DeciderError> — pure function, no async, no side-effects
  • CreateProject: rejects duplicate paths, emits ProjectCreated
  • DeleteProject: rejects unknown id, emits ProjectDeleted
  • RenameProject: rejects unknown id, emits ProjectRenamed
  • CreateThread: validates project exists, emits ThreadCreated
  • DeleteThread: validates thread exists, emits ThreadDeleted
  • SelectThread: validates thread exists, emits ThreadSelected
  • StartTurn: validates thread exists and is Idle, emits TurnStarted + ThreadStatusChanged(Running)
  • InterruptTurn: validates thread is Running, emits TurnInterrupted + ThreadStatusChanged(Idle)
  • IngestProviderEvent: maps ProviderEvent variants to domain events (MessageAppended, MessageChunkReceived, TurnCompleted, etc.)
  • CreateCheckpoint: emits CheckpointCreated
  • DeciderError enum with descriptive variants

Projector (orchestration/projector.rs)

  • fn project(model, event) -> OrchestrationReadModel — pure fold function
  • Handles all OrchestrationEvent variants: creates/deletes/renames projects, creates threads, updates status, appends turns and messages, records checkpoints

Tests

  • Decider: at least one positive + one negative test per command variant
  • Projector: round-trip tests (create event → project → verify read model state)
  • Integration: decide → project chain produces consistent read model

Out of scope

  • EventStore persistence (see SQLite persistence story)
  • OrchestrationEngine wiring (see engine story)
  • Reactor side-effects

References

  • Spec §5 (Event Sourcing & CQRS)
  • Spec §7.1 (ProviderEvent types)

Dependencies

## User story As a **backend developer**, I want pure-function implementations of the Decider (command validation → events) and Projector (event folding → read model), so that the core business logic is fully testable without I/O and serves as the foundation for the OrchestrationEngine. ## Acceptance criteria ### Commands (`orchestration/commands.rs`) - [ ] `OrchestrationCommand` enum with all variants from spec §5.1: - Project: `CreateProject`, `DeleteProject`, `RenameProject` - Thread: `CreateThread`, `DeleteThread`, `SelectThread` - Turn: `StartTurn`, `InterruptTurn`, `ApprovePendingAction`, `RejectPendingAction` - Provider: `IngestProviderEvent` - Checkpoint: `CreateCheckpoint`, `RestoreCheckpoint` - Git: `CommitChanges`, `CreateBranch`, `PushBranch` ### Events (`orchestration/events.rs`) - [ ] `OrchestrationEvent` enum with all variants from spec §5.2 (ProjectCreated, ThreadCreated, TurnStarted, MessageAppended, MessageChunkReceived, CheckpointCreated, etc.) - [ ] `StoredEvent` wrapper with `sequence: u64`, `event: OrchestrationEvent`, `stored_at: DateTime<Utc>` - [ ] `ProviderEvent` enum (TextDelta, ToolCall, ToolResult, ThinkingDelta, TurnComplete, Error, AwaitingApproval) as per spec §7.1 ### Decider (`orchestration/decider.rs`) - [ ] `fn decide(command, model) -> Result<Vec<OrchestrationEvent>, DeciderError>` — pure function, no async, no side-effects - [ ] `CreateProject`: rejects duplicate paths, emits `ProjectCreated` - [ ] `DeleteProject`: rejects unknown id, emits `ProjectDeleted` - [ ] `RenameProject`: rejects unknown id, emits `ProjectRenamed` - [ ] `CreateThread`: validates project exists, emits `ThreadCreated` - [ ] `DeleteThread`: validates thread exists, emits `ThreadDeleted` - [ ] `SelectThread`: validates thread exists, emits `ThreadSelected` - [ ] `StartTurn`: validates thread exists and is `Idle`, emits `TurnStarted` + `ThreadStatusChanged(Running)` - [ ] `InterruptTurn`: validates thread is `Running`, emits `TurnInterrupted` + `ThreadStatusChanged(Idle)` - [ ] `IngestProviderEvent`: maps `ProviderEvent` variants to domain events (MessageAppended, MessageChunkReceived, TurnCompleted, etc.) - [ ] `CreateCheckpoint`: emits `CheckpointCreated` - [ ] `DeciderError` enum with descriptive variants ### Projector (`orchestration/projector.rs`) - [ ] `fn project(model, event) -> OrchestrationReadModel` — pure fold function - [ ] Handles all `OrchestrationEvent` variants: creates/deletes/renames projects, creates threads, updates status, appends turns and messages, records checkpoints ### Tests - [ ] Decider: at least one positive + one negative test per command variant - [ ] Projector: round-trip tests (create event → project → verify read model state) - [ ] Integration: decide → project chain produces consistent read model ## Out of scope - EventStore persistence (see SQLite persistence story) - OrchestrationEngine wiring (see engine story) - Reactor side-effects ## References - Spec §5 (Event Sourcing & CQRS) - Spec §7.1 (ProviderEvent types) ## Dependencies - **Blocked by:** none - **Blocks:** #5, #6, #7, #9, #11, #12, #15 - **Branch off:** `main` - **Full graph:** #21
claude-desktop added this to the v0.1.0 milestone 2026-04-16 11:28:21 +00:00
Sign in to join this conversation.
No description provided.