Skip to content

fix(core): make compound domain commands atomic or explicitly recoverable #1969

Description

@hmans

Problem

Several user-facing commands still span multiple writes even though callers experience them as one operation:

  • CreateRoom atomically publishes the room and default permission facts, then performs group placement through a separate best-effort MoveRoomToGroup. It can return a room whose response carries group_id while the durable group layout never acquired that room.
  • RoomCommandModel.UpdateRoom writes name/description and universal through two separate commits. The first can succeed while the second fails.
  • Posting a thread reply with also_send_to_channel commits the canonical reply first, then appends its channel echo separately and logs failure while returning success.

These partial-success states are durable. A request retry is not currently guaranteed to repair them and may create additional facts.

Thread creation, message body/post facts, default room permissions, and video-processing start facts already demonstrate that related EVT facts can be committed in one atomic batch.

Goal

Define the transaction boundary of each compound command. Facts required for the command’s promised result should commit atomically under the relevant OCC guards. Work that is intentionally asynchronous should have an explicit durable trigger, recovery policy, and observable status.

Acceptance criteria

  • Decide and document the promised outcome for room creation, multi-field room updates, and thread-reply channel echoes.
  • Room creation cannot report successful placement in a group unless room creation, required defaults, and group membership have committed consistently.
  • Updating metadata plus universal cannot leave only a caller-invisible prefix of the requested patch committed.
  • A successful “also send to channel” command cannot permanently omit the requested echo without exposing an explicit pending/failure result and durable recovery path.
  • Every atomic batch carries OCC guards for all invariants it spans and reruns the complete decision after conflicts.
  • Projection waits cover every read model needed by the returned resource and subsequent live invalidation.
  • Multi-replica race tests and failure injection cover every boundary between the component writes.
  • Rolling-deploy and rollback behavior is documented before changing persisted write patterns.
  • Architecture inventory and relevant FDRs are updated; split implementation work into focused child issues if one PR would be too broad.

Non-goals

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions