%%%------------------------------------------------------------------- %%% @doc %%% Behaviour definition for neuroevolution lineage event persistence. %%% %%% This behaviour defines the complete API for lineage tracking: %%% - Required callbacks: Event store operations (persist, read, subscribe) %%% - Optional callbacks: Query operations (trees, trajectories, histories) %%% %%% == Architecture == %%% %%% The behaviour separates concerns following CQRS principles: %%% %%% ``` %%% REQUIRED (Event Store) OPTIONAL (Queries) %%% ---------------------- ------------------ %%% persist_event/2 get_breeding_tree/2 %%% persist_batch/2 get_fitness_trajectory/2 %%% read_stream/3 get_mutation_history/2 %%% subscribe/3 get_knowledge_transfers/2 %%% unsubscribe/3 get_by_causation/2 %%% ''' %%% %%% Implementations should: %%% - Use an event store for required callbacks %%% - Use projections (read models) for optional query callbacks %%% %%% == Stream Design == %%% %%% Events are organized into streams based on entity type: %%% - `individual-{id}' - Birth, death, fitness, mutations %%% - `species-{id}' - Speciation, lineage events %%% - `population-{id}' - Generation, capacity events %%% - `coalition-{id}' - Coalition lifecycle %%% %%% @end %%%------------------------------------------------------------------- -module(neuroevolution_lineage_events). %%% ============================================================================ %%% Type Definitions %%% ============================================================================ -type event() :: map(). -type stream_id() :: binary(). -type position() :: non_neg_integer(). -type direction() :: forward | backward. -type individual_id() :: binary(). -type causation_id() :: binary(). -type read_opts() :: #{ from => position(), limit => pos_integer(), direction => direction() }. -type tree_node() :: #{ individual_id := individual_id(), parents => [tree_node()], birth_event => event() }. -type trajectory_point() :: {Timestamp :: integer(), Fitness :: float()}. -export_type([ event/0, stream_id/0, position/0, direction/0, read_opts/0, individual_id/0, causation_id/0, tree_node/0, trajectory_point/0 ]). %%% ============================================================================ %%% Required Callbacks - Event Store Operations %%% ============================================================================ %% @doc Initialize the event store backend. -callback init(Config :: map()) -> {ok, State :: term()} | {error, Reason :: term()}. %% @doc Persist a single event to the appropriate stream. %% The implementation should route the event based on event_type. -callback persist_event(Event :: event(), State :: term()) -> ok | {error, Reason :: term()}. %% @doc Persist a batch of events (for efficiency). %% Events may be routed to different streams based on their type. -callback persist_batch(Events :: [event()], State :: term()) -> ok | {error, Reason :: term()}. %% @doc Read raw events from a stream. %% Returns events in order, optionally filtered by position and limit. -callback read_stream(StreamId :: stream_id(), Opts :: read_opts(), State :: term()) -> {ok, Events :: [event()]} | {error, Reason :: term()}. %% @doc Subscribe to new events on a stream (for projections). %% The subscriber receives {lineage_event, StreamId, Event} messages. -callback subscribe(StreamId :: stream_id(), Pid :: pid(), State :: term()) -> ok | {error, Reason :: term()}. %% @doc Unsubscribe from a stream. -callback unsubscribe(StreamId :: stream_id(), Pid :: pid(), State :: term()) -> ok | {error, Reason :: term()}. %%% ============================================================================ %%% Optional Callbacks - Query Operations (via Projections) %%% ============================================================================ %%% %%% These queries return derived data built from events. Implementations %%% should use internal projections (read models) to satisfy these queries, %%% NOT scan raw events on every call. %%% %%% Simple backends (e.g., mock for testing) may return {error, not_implemented}. -optional_callbacks([ get_breeding_tree/3, get_fitness_trajectory/2, get_mutation_history/2, get_knowledge_transfers/2, get_by_causation/2 ]). %% @doc Get the breeding tree (ancestry) for an individual. %% %% Returns a tree structure showing parents, grandparents, etc. %% The Depth parameter controls how many generations to include. %% %% Implementations should query a lineage tree projection. -callback get_breeding_tree(IndividualId :: individual_id(), Depth :: pos_integer(), State :: term()) -> {ok, Tree :: tree_node()} | {error, Reason :: term()}. %% @doc Get the fitness trajectory for an individual. %% %% Returns a time-ordered list of (timestamp, fitness) pairs showing %% how the individual's fitness changed over evaluations. %% %% Implementations should query a fitness trajectory projection. -callback get_fitness_trajectory(IndividualId :: individual_id(), State :: term()) -> {ok, Trajectory :: [trajectory_point()]} | {error, Reason :: term()}. %% @doc Get the mutation history for an individual. %% %% Returns all mutation events (neuron_added, connection_removed, %% weight_perturbed, etc.) for the individual in chronological order. %% %% Implementations should query a mutation history projection. -callback get_mutation_history(IndividualId :: individual_id(), State :: term()) -> {ok, Mutations :: [event()]} | {error, Reason :: term()}. %% @doc Get knowledge transfer events involving an individual. %% %% Returns events where the individual was either mentor or student %% in knowledge transfer, skill imitation, or weight grafting. %% %% Implementations should query a knowledge transfer projection. -callback get_knowledge_transfers(IndividualId :: individual_id(), State :: term()) -> {ok, Transfers :: [event()]} | {error, Reason :: term()}. %% @doc Get all events with a specific causation ID. %% %% Useful for tracing related events (e.g., all events from one %% evaluation batch, or all mutations from one breeding cycle). %% %% Implementations may scan events or use a causation index. -callback get_by_causation(CausationId :: causation_id(), State :: term()) -> {ok, Events :: [event()]} | {error, Reason :: term()}.