diff --git a/specs/001-port-message-module/checklists/requirements.md b/specs/001-port-message-module/checklists/requirements.md new file mode 100644 index 0000000..a5f1086 --- /dev/null +++ b/specs/001-port-message-module/checklists/requirements.md @@ -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 diff --git a/specs/001-port-message-module/spec.md b/specs/001-port-message-module/spec.md new file mode 100644 index 0000000..bb88e0d --- /dev/null +++ b/specs/001-port-message-module/spec.md @@ -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)