Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions specs/001-port-message-module/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Specification Quality Checklist: Port Message Module

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-11-30
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- Specification is complete and ready for `/speckit.clarify` or `/speckit.plan`
- The spec ports the Ruby Eventide Message module to Elixir, covering all major submodules: Build, Copy, Follow, Transform, Info, Correlate
- Metadata struct is referenced but will need its own specification for full implementation
- Dependencies on vrt-schema and vrt-message-store are documented
208 changes: 208 additions & 0 deletions specs/001-port-message-module/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Feature Specification: Port Message Module

**Feature Branch**: `001-port-message-module`
**Created**: 2025-11-30
**Status**: Draft
**Input**: User description: "Port eventide/gems/messaging/lib/messaging/message.rb to the library vrt-messaging"

## Clarifications

### Session 2025-11-30

- Q: Should the Message module be namespaced under `Verity.Messaging` to match the existing DSL? → A: Yes, use `Verity.Messaging.Message` to match existing DSL namespace

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Define a Message Struct (Priority: P1)

As a developer building an event-sourced application, I want to define message structs that automatically include standard messaging capabilities (id, metadata, type info) so that I can create typed messages without boilerplate code.

**Why this priority**: This is the core functionality that enables all other messaging features. Without the ability to define message structs with automatic attribute handling, no other messaging functionality can work.

**Independent Test**: Can be fully tested by defining a message struct with custom attributes and verifying that it includes id, metadata, and message type/name functions.

**Acceptance Scenarios**:

1. **Given** I have a module that uses `Verity.Messaging.Message`, **When** I define custom attributes using `attribute/2`, **Then** the message struct includes those attributes plus automatic `id` and `metadata` fields
2. **Given** I have a message struct, **When** I call `message_type/1`, **Then** it returns the module name without namespace (e.g., `Deposited`)
3. **Given** I have a message struct, **When** I call `message_name/1`, **Then** it returns the snake_cased version of the type (e.g., `deposited`)

**Example Usage**:
```elixir
defmodule MyApp.Messages.Events.Deposited do
use Verity.Messaging.Message

attribute(:deposit_id, String.t())
attribute(:account_id, String.t())
attribute(:amount, float())
end
```

---

### User Story 2 - Build Messages from Data (Priority: P1)

As a developer, I want to build message instances from maps/data so that I can easily construct messages from external data sources (database, API responses, etc.).

**Why this priority**: Building messages from data is essential for message reconstruction from storage and for creating messages programmatically. This is fundamental infrastructure.

**Independent Test**: Can be tested by calling `build/2` with a data map and metadata map, verifying the returned struct has all expected values populated.

**Acceptance Scenarios**:

1. **Given** a message module, **When** I call `build/0` with no arguments, **Then** I receive a new message instance with default metadata
2. **Given** a message module, **When** I call `build/1` with a data map, **Then** the message attributes are populated from the map
3. **Given** a message module, **When** I call `build/2` with data and metadata maps, **Then** both message attributes and metadata are populated

---

### User Story 3 - Copy Message Attributes (Priority: P2)

As a developer, I want to copy attributes from one message to another so that I can create related messages that share common data without manual field-by-field copying.

**Why this priority**: Copy functionality enables message workflows where related messages share data. Important but builds on the foundational message struct functionality.

**Independent Test**: Can be tested by creating a source message with attributes, calling copy to a new message type, and verifying attributes are transferred correctly.

**Acceptance Scenarios**:

1. **Given** a source message with attributes, **When** I call `copy/2` to a new message, **Then** the source attributes are copied to the receiver
2. **Given** a source message, **When** I call `copy/2` with `include: [:specific_attr]`, **Then** only the specified attributes are copied
3. **Given** a source message, **When** I call `copy/2` with `exclude: [:some_attr]`, **Then** all attributes except the excluded ones are copied
4. **Given** a source message, **When** I call `copy/2` with `metadata: true`, **Then** metadata is also copied including properties

---

### User Story 4 - Follow Message Chains (Priority: P2)

As a developer, I want to create messages that follow (are caused by) other messages so that I can establish causation chains for event sourcing workflows.

**Why this priority**: Following/causation is a core event sourcing pattern that enables workflow tracking and debugging. Required for proper event chain management.

**Independent Test**: Can be tested by creating a preceding message, calling `follow/2` to create a subsequent message, and verifying causation metadata is set correctly.

**Acceptance Scenarios**:

1. **Given** a preceding message with metadata, **When** I call `follow/2` to create a subsequent message, **Then** the subsequent message's metadata has causation fields set to the preceding message's identifiers
2. **Given** a subsequent message, **When** I call `follows?/1` with the preceding message, **Then** it returns true if the causation chain is valid
3. **Given** two unrelated messages, **When** I call `follows?/1`, **Then** it returns false

---

### User Story 5 - Correlate Messages (Priority: P3)

As a developer, I want to create messages that are correlated to a specific stream so that I can track which workflow/process a message belongs to.

**Why this priority**: Correlation enables grouping related messages across different streams. Important for workflow tracking but lower priority than basic message creation and causation.

**Independent Test**: Can be tested by calling `correlate/1` with a stream name and verifying the returned message has the correlation_stream_name set.

**Acceptance Scenarios**:

1. **Given** a message module, **When** I call `correlate/1` with a stream name, **Then** a new message is created with that stream name as the correlation stream
2. **Given** a message with correlation_stream_name set, **When** I check correlation, **Then** the correlation stream name is accessible via metadata

---

### User Story 6 - Transform Messages to/from MessageData (Priority: P2)

As a developer, I want to transform messages to and from the MessageData format used by the message store so that messages can be persisted and retrieved correctly.

**Why this priority**: Transformation is essential for persistence. Messages must be serializable to/from the message store format for any real-world usage.

**Independent Test**: Can be tested by creating a message, transforming to MessageData format, then transforming back, and verifying roundtrip fidelity.

**Acceptance Scenarios**:

1. **Given** a message instance, **When** I transform it to MessageData write format, **Then** it produces a map with id, type, data, and metadata fields
2. **Given** MessageData from the store, **When** I transform it to a message instance, **Then** the message is properly hydrated with all fields including metadata enrichment (stream_name, position, time, etc.)

---

### Edge Cases

- What happens when building a message with nil data? (Should create message with default values)
- What happens when copying with `strict: true` and source is missing attributes? (Should raise error)
- What happens when following a message with nil metadata fields? (Should handle gracefully, not crash)
- How does transformation handle empty properties hashes? (Should omit from serialized output)
- What happens when message_type is called on a deeply nested module? (Should return only the last segment)

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: System MUST provide `Verity.Messaging.Message` behaviour that can be used by other modules to define message structs via `use Verity.Messaging.Message`
- **FR-002**: Message structs MUST automatically include `id` (String) and `metadata` (Metadata struct) fields as transient attributes
- **FR-003**: System MUST provide `attribute/2` macro for defining typed message attributes (e.g., `attribute(:name, String.t())`)
- **FR-004**: System MUST provide `message_type/1` function returning the unqualified module name
- **FR-005**: System MUST provide `message_name/1` function returning the snake_cased message type
- **FR-006**: System MUST provide `build/0`, `build/1`, and `build/2` functions for message construction
- **FR-007**: System MUST provide `Verity.Messaging.Message.Copy.copy/2` with options for `include`, `exclude`, `metadata`, and `strict`
- **FR-008**: System MUST provide `Verity.Messaging.Message.Follow.follow/2` that establishes causation metadata links
- **FR-009**: System MUST provide `follows?/1` instance function to verify causation chains
- **FR-010**: System MUST provide `correlate/1` to create messages with correlation stream names
- **FR-011**: System MUST provide `Verity.Messaging.Message.Transform` module with `MessageData.write/1` and `MessageData.read/1` for serialization
- **FR-012**: The `Copy` module MUST raise an error when `strict: true` and source attributes are missing in receiver
- **FR-013**: Properties copying MUST use deep copy to avoid shared references between messages

### Explicit Dependencies & Configuration *(mandatory)*

- **Dependency**: `vrt-schema` - Provides `Schema.DataStructure` behaviour for attribute definition. Must be available as a dependency. Tests will fail if `Schema.DataStructure` is not defined.
- **Dependency**: `vrt-message-store` (for Transform) - Provides `MessageStore.MessageData.Write` struct for serialization. Transform tests will fail without this dependency.
- **Configuration**: None required - this module has no runtime configuration needs.

### Key Entities *(include if feature involves data)*

- **Verity.Messaging.Message**: A behaviour module that when `use`d adds messaging capabilities to a struct. Contains id (UUID string), metadata (Metadata struct), and custom attributes defined by the implementing module via `attribute/2` macro.
- **Verity.Messaging.Message.Metadata**: A struct containing message tracking fields: stream_name, position, global_position, causation fields, correlation_stream_name, reply_stream_name, properties (map), local_properties (map), time, and schema_version.
- **Verity.Messaging.Message.Copy**: A module providing attribute copying between messages with filtering options.
- **Verity.Messaging.Message.Follow**: A module providing causation chain establishment between messages.
- **Verity.Messaging.Message.Transform**: A module providing bidirectional conversion between Message structs and MessageData storage format.
- **Verity.Messaging.Message.Info**: A module providing message type/name introspection functions.
- **Verity.Messaging.Message.Build**: A module providing message construction from data maps.
- **Verity.Messaging.Message.Correlate**: A module providing correlation stream assignment.

## Test Plan *(mandatory before implementation)*

### Unit Tests *(write these first)*

- Test `message_type/1` returns unqualified module name for simple and nested modules
- Test `message_name/1` returns snake_cased type name
- Test `build/0` creates message with default metadata
- Test `build/1` populates attributes from data map
- Test `build/2` populates both attributes and metadata
- Test `Copy.copy/2` copies all attributes by default
- Test `Copy.copy/2` with `include` option copies only specified attributes
- Test `Copy.copy/2` with `exclude` option omits specified attributes
- Test `Copy.copy/2` with `metadata: true` copies metadata and properties
- Test `Copy.copy/2` with `strict: true` raises on missing attributes
- Test `Follow.follow/2` sets causation metadata fields
- Test `follows?/1` returns true for valid causation chain
- Test `follows?/1` returns false for unrelated messages
- Test `correlate/1` creates message with correlation_stream_name set
- Test `Transform.MessageData.write/1` produces correct map structure
- Test `Transform.MessageData.read/1` hydrates message from stored data
- Test transient_attributes returns `[:id, :metadata]`

### Integration Tests *(required for each cross-boundary interaction)*

- Test roundtrip: message -> MessageData.write -> MessageData.read -> message preserves all data
- Test copy followed by follow maintains distinct metadata references
- Test message workflow: create -> copy -> follow chain produces valid causation tracking

## Failure Modes & Observability *(mandatory)*

- **Copy Error**: When `strict: true` and attributes are missing, raise `Verity.Messaging.Message.Copy.Error` with descriptive message indicating which attributes are missing
- **Invalid Attribute Access**: Attempting to access undefined attributes should raise standard Elixir errors
- **Metadata Property Errors**: Setting non-atom property names should raise `Verity.Messaging.Message.Metadata.Error`
- **Logging**: No special logging required - this is a data structure module
- **Tracing**: No distributed tracing required - pure local operations

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Developers can define a message struct with custom attributes in under 5 lines of code
- **SC-002**: All message operations (build, copy, follow, transform) complete in under 1 millisecond for typical message sizes
- **SC-003**: 100% of Eventide Message module public API is available in the ported version
- **SC-004**: Message roundtrip through Transform produces identical data (excluding transient fields populated by storage)