Skip to content

Overall architecture

How things work and where data goes

This document describes the high-level design of the DetectMateLibrary, how components interact, the data contracts they use, and guidance for deploying and extending the system. The library is built around small, composable components that operate on streaming log data and exchange strongly-typed Schema objects.

Key goals:

  • Clear separation of concerns (reading, parsing, detection, output).
  • Stream-friendly processing with minimal buffering.
  • Well-defined schema contracts so components can be composed or run as microservices.
  • Easy extensibility: add new readers, parsers or detectors by subclassing core base classes.

Components flow

The pipeline is strictly directional:

  • Parser: consumes raw logs and produces parsed log objects (structured fields, timestamps, variables).
  • Detector: consumes parsed logs and generates alerts/findings when rules or models match anomalous behavior.
  • Alert Aggregation: consumes alerts and aggregates them.

Each arrow represents a stream of Schema objects. Components are designed to run in the same process for lightweight setups or as separate services for scalable deployments.

Components flow

Components architecture

All components inherit from a CoreComponent class. This class provides all the essential functionality required for DetectMate to operate (see UML diagram below). Every Detector must inherit from CoreDetector, every AlertAggregator must inherit from CoreAlertAggregation and every Parser must inherit from CoreParser to ensure compatibility with DetectMate.

Each component's arguments must be stored in its corresponding configuration class. These config classes follow the same design pattern as their components and must inherit from CoreConfig.

Architecture

Components methods

Each Core* base class exposes a small, stable API that implementations must implement or may override.

class ConfigComponent(CoreConfig):
    """Contains all the arguments of the component."""


class Component(CoreComponent):
    def run(
        self, input_: List[BaseSchema] | BaseSchema, output_: BaseSchema
    ) -> bool:
        """Run the component for a specific input."""
        pass

    def train(
        self, input_: List[BaseSchema] | BaseSchema,
    ) -> None:
        """Train the component with a specific input."""
        pass

    def update_state(self, state: StatesL) -> None:
        """Update the current state by request of the user.

        states:
        *   keep_training: force to keep training
        *   stop_training: force to stop training
        *   keep_configuring: force to keep configuring
        *   stop_configuring: force to stop configuring
        """
        pass

    def get_state(self) -> str:
        """Return the current state of the component.

        states:
        *   Configuring: the component is doing configurations
        *   Training: the component is training
        *   Default: the component is just processing data
        """
        pass

    def export_state(
        self, path: str | None = None, storage_options: dict[str, Any] | None = None,
    ) -> bytes | None:
        """Export the current state; None for a stateless component."""
        pass

    def import_state(
        self, path: str | bytes, storage_options: dict[str, Any] | None = None
    ) -> None:
        """Import a state; a no-op for a stateless component."""
        pass

    def process(self, data: BaseSchema | bytes) -> BaseSchema | bytes | None:
        """Process the data in a stream fashion (Defined in the
        CoreComponent)"""
        pass

    def get_config(self) -> Dict[str, Any]:
        """Get the configuration of the component (Defined in the
        CoreComponent)"""
        pass

    def update_config(self, new_config: Dict[str, Any]) -> None:
        """Update the configuration of the component (Defined in the
        CoreComponent)"""
        pass

    def get_window_size(self) -> int:
        """Get window size of the data buffer."""
        pass

    def stack(self, other: object | list[object | bytes] | bytes) -> None:
        """(Federation only) stack multiple components for federation tasks."""
        pass

    def aggregate(self, unstack: bool = False) -> None | bytes:
        """(Federation only) aggregate multiple components."""
        pass

    def to_binary(self) -> bytes | None:
        """(Federation only) fill it to be compatible with federation ops."""
        pass

    def from_binary(self, binary: bytes) -> object:
        """(Federation only) fill it to be compatible with federation ops."""
        pass

    def aggregate_strategy(self, components: set["FedOperations"]) -> None:
        """(Federation only) fill it to be compatible with federation ops."""
        pass

Go back Index