{"id":51792437,"url":"https://github.com/kanutocd/whodunnit-chronicles","last_synced_at":"2026-07-20T23:37:56.442Z","repository":{"id":305753693,"uuid":"1023679312","full_name":"kanutocd/whodunnit-chronicles","owner":"kanutocd","description":"whodunit-chronicles - the complete historical record of your data of whodunit did what","archived":false,"fork":false,"pushed_at":"2026-05-19T22:04:37.000Z","size":885,"stargazers_count":1,"open_issues_count":15,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-05T15:20:17.778Z","etag":null,"topics":["audit-trail","audited","data-analytics","mariadb-binlog-events","paper-trail","postgres-replication-log","realtime-streaming","rubygem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kanutocd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-21T14:23:10.000Z","updated_at":"2026-06-28T20:53:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"59b559dc-5e99-4bab-9fcc-266ec51bb06b","html_url":"https://github.com/kanutocd/whodunnit-chronicles","commit_stats":null,"previous_names":["kanutocd/whodunit-chronicles"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/kanutocd/whodunnit-chronicles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanutocd%2Fwhodunnit-chronicles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanutocd%2Fwhodunnit-chronicles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanutocd%2Fwhodunnit-chronicles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanutocd%2Fwhodunnit-chronicles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kanutocd","download_url":"https://codeload.github.com/kanutocd/whodunnit-chronicles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanutocd%2Fwhodunnit-chronicles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35703527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"ssl_error","status_checked_at":"2026-07-20T02:08:09.736Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit-trail","audited","data-analytics","mariadb-binlog-events","paper-trail","postgres-replication-log","realtime-streaming","rubygem"],"created_at":"2026-07-20T23:37:55.782Z","updated_at":"2026-07-20T23:37:56.436Z","avatar_url":"https://github.com/kanutocd.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📜 Whodunit Chronicles\n\n[![Gem Version](https://badge.fury.io/rb/whodunit-chronicles.svg)](https://badge.fury.io/rb/whodunit-chronicles)\n[![CI](https://github.com/kanutocd/whodunit-chronicles/workflows/CI/badge.svg)](https://github.com/kanutocd/whodunit-chronicles/actions)\n[![Coverage Status](https://codecov.io/gh/kanutocd/whodunit-chronicles/branch/main/graph/badge.svg)](https://codecov.io/gh/kanutocd/whodunit-chronicles)\n[![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.1.0-ruby.svg)](https://www.ruby-lang.org/en/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003e **The complete historical record of your _Whodunit Dun Wat?_ data**\n\n\u003e **💡 Origin Story:** Chronicles is inspired by the challenge of streaming database changes for real-time analytics without impacting application performance. The concept proved so effective in a previous project that it became the foundation for this Ruby implementation.\n\nWhile [Whodunit](https://github.com/kanutocd/whodunit) tracks _who_ made changes, **Chronicles** captures _what_ changed by streaming database events into comprehensive audit trails with **zero Rails application overhead**.\n\n## ✨ Features\n\n- **🚄 Zero-Latency Streaming**: PostgreSQL logical replication + MySQL/MariaDB binary log streaming\n- **🔄 Zero Application Overhead**: No Rails callbacks or Active Record hooks required\n- **🏗️ Database Agnostic**: Abstract adapter pattern supports PostgreSQL and MySQL/MariaDB\n- **⚡ Thread-Safe**: Concurrent processing with configurable thread pools\n- **🛡️ Resilient**: Built-in error handling, retry logic, and monitoring\n- **📊 Complete Audit Trail**: Captures INSERT, UPDATE, DELETE with full before/after data\n- **🧪 Code Coverage**: 94%+ test coverage with comprehensive error scenarios\n\n## 🚀 Quick Start\n\n### 🎯 Usage Scenarios\n\nChronicles excels at transforming database changes into business intelligence. Here are two common patterns:\n\n#### 1. Basic Audit Trail Integration\n\n---\n\nPerfect for applications that need comprehensive change tracking alongside Whodunit's user attribution:\n\n```ruby\n# Basic setup for user activity tracking\nclass BasicProcessor \u003c Whodunit::Chronicles::Processor\n  def build_chronicles_record(change_event)\n    super.tap do |record|\n      # Add basic business context\n      record[:change_category] = categorize_change(change_event)\n      record[:business_impact] = assess_impact(change_event)\n    end\n  end\n\n  private\n\n  def categorize_change(change_event)\n    case change_event.table_name\n    when 'users' then 'user_management'\n    when 'posts' then 'content'\n    when 'comments' then 'engagement'\n    else 'system'\n    end\n  end\nend\n```\n\n**Use Case**: Blog platform tracking user posts and comments for community management and content moderation.\n\n#### 2. Advanced Recruitment Analytics\n\nSophisticated business intelligence for talent acquisition platforms:\n\n```ruby\n# Advanced processor for recruitment metrics\nclass RecruitmentAnalyticsProcessor \u003c Whodunit::Chronicles::Processor\n  def build_chronicles_record(change_event)\n    super.tap do |record|\n      # Add recruitment-specific business metrics\n      record[:recruitment_stage] = determine_stage(change_event)\n      record[:funnel_position] = calculate_funnel_position(change_event)\n      record[:time_to_hire_impact] = assess_time_impact(change_event)\n      record[:cost_per_hire_impact] = calculate_cost_impact(change_event)\n\n      # Campaign attribution\n      record[:utm_source] = extract_utm_source(change_event)\n      record[:campaign_id] = extract_campaign_id(change_event)\n\n      # Quality metrics\n      record[:candidate_quality_score] = assess_candidate_quality(change_event)\n    end\n  end\n\n  def process(change_event)\n    record = build_chronicles_record(change_event)\n    store_audit_record(record)\n\n    # Stream to analytics platforms\n    stream_to_prometheus(record) if track_metrics?\n    update_grafana_dashboard(record)\n    trigger_real_time_alerts(record) if alert_worthy?(record)\n  end\n\n  private\n\n  def determine_stage(change_event)\n    return 'unknown' unless change_event.table_name == 'applications'\n\n    case change_event.new_data\u0026.dig('status')\n    when 'submitted' then 'application'\n    when 'screening', 'in_review' then 'screening'\n    when 'interview_scheduled', 'interviewed' then 'interview'\n    when 'offer_extended', 'offer_accepted' then 'offer'\n    when 'hired' then 'hire'\n    else 'unknown'\n    end\n  end\n\n  def stream_to_prometheus(record)\n    # Track key recruitment metrics\n    RECRUITMENT_APPLICATIONS_TOTAL.increment(\n      source: record[:utm_source],\n      department: record.dig(:new_data, 'department')\n    )\n\n    if record[:action] == 'UPDATE' \u0026\u0026 status_changed_to_hired?(record)\n      RECRUITMENT_HIRES_TOTAL.increment(\n        source: record[:utm_source],\n        time_to_hire: record[:time_to_hire_impact]\n      )\n    end\n  end\n\n  def update_grafana_dashboard(record)\n    # Send time-series data for Grafana visualization\n    InfluxDB.write_point('recruitment_events', {\n      timestamp: record[:occurred_at],\n      table: record[:table_name],\n      action: record[:action],\n      stage: record[:recruitment_stage],\n      source: record[:utm_source],\n      cost_impact: record[:cost_per_hire_impact],\n      quality_score: record[:candidate_quality_score]\n    })\n  end\nend\n```\n\n**Use Case**: Imagine a Spherical Cow Talent acquisition platform tracking candidate journey from application through hire, with real-time dashboards showing conversion rates, time-to-hire, cost-per-hire, and source effectiveness.\n\n#### 📊 Visual Analytics Dashboard\n\nThe recruitment analytics processor creates comprehensive Grafana dashboards for executive reporting and operational insights:\n\n\u003cdiv align=\"center\"\u003e\n\n**Campaign Performance Analytics**\n\u003ca href=\"examples/images/campaign-performance-analytics.png\" title=\"Click to view full size image\"\u003e\n\u003cimg src=\"examples/images/campaign-performance-analytics.png\" width=\"300\" /\u003e\n\u003c/a\u003e\n_Track campaign ROI, cost-per-hire by channel, and conversion rates across marketing sources_\n\n**Candidate Journey Analytics**\n\u003ca href=\"examples/images/candidate-journey-analytics.png\" title=\"Click to view full size image\"\u003e\n\u003cimg src=\"examples/images/candidate-journey-analytics.png\" width=\"300\" /\u003e\n\u003c/a\u003e\n_Monitor candidate engagement, funnel conversion rates, and application completion patterns_\n\n**Recruitment Funnel Analytics**\n\u003ca href=\"examples/images/recruitment-funnel-analytics.png\" title=\"Click to view full size image\"\u003e\n\u003cimg src=\"examples/images/recruitment-funnel-analytics.png\" width=\"300\" /\u003e\n\u003c/a\u003e\n_Analyze hiring pipeline progression, department performance, and time-series trends_\n\n\u003c/div\u003e\n\nThese dashboards are automatically populated by Chronicles as candidates move through your hiring funnel, providing real-time visibility into recruitment performance without any manual data entry.\n\n### Installation\n\nAdd to your Gemfile:\n\n```ruby\ngem 'whodunit-chronicles'\n```\n\nOr install directly:\n\n```bash\ngem install whodunit-chronicles\n```\n\n### Basic Usage\n\n```ruby\nrequire 'whodunit/chronicles'\n\n# Database Configuration\n\n## PostgreSQL Configuration\nWhodunit::Chronicles.configure do |config|\n  config.adapter = :postgresql\n  config.database_url = 'postgresql://localhost/myapp_production'\n  config.audit_database_url = 'postgresql://localhost/myapp'\n  config.publication_name = 'myapp_chronicles'\n  config.replication_slot_name = 'myapp_chronicles_slot'\nend\n\n## MySQL/MariaDB Configuration\nWhodunit::Chronicles.configure do |config|\n  config.adapter = :mysql\n  config.database_url = 'mysql://user:password@localhost/myapp_production'\n  config.audit_database_url = 'mysql://user:password@localhost/myapp_audit'\n  config.mysql_server_id = 1001  # Unique server ID for replication\nend\n\n# Create and start the service\nservice = Whodunit::Chronicles.service\nservice.setup!  # Create publication/replication setup\nservice.start   # Begin streaming changes\n\n# Service runs in background threads\nsleep 10\n\n# Stop gracefully\nservice.stop\nservice.teardown!  # Clean up database objects\n```\n\n## 🏗️ Architecture\n\nChronicles uses **PostgreSQL logical replication** and **MySQL/MariaDB binary log streaming** to capture database changes without impacting your application:\n\n```\n┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐\n│   Application   │    │    Chronicles    │    │   Audit Store   │\n│   Database      │───▶│     Service      │───▶│   Database      │\n│                 │    │                  │    │                 │\n│ • Users         │    │ • Stream Adapter │    │ • audit_records │\n│ • Posts         │    │ • Event Parser   │    │ • Searchable    │\n│ • Comments      │    │ • Audit Builder  │    │ • Reportable    │\n└─────────────────┘    └──────────────────┘    └─────────────────┘\n        │                       │\n        │              ┌────────▼────────┐\n        │              │  PostgreSQL     │\n        └──────────────│  Logical        │\n                       │  Replication    │\n                       └─────────────────┘\n```\n\n### Core Components\n\n- **StreamAdapter**: Database-specific change streaming (PostgreSQL logical replication, MySQL/MariaDB binary log streaming)\n- **ChangeEvent**: Unified change representation across adapters\n- **Processor**: Transforms changes into searchable audit records\n- **Service**: Orchestrates streaming with error handling and retry logic\n\n## ⚙️ Configuration\n\n```ruby\nWhodunit::Chronicles.configure do |config|\n  # Database connections\n  config.database_url = ENV['DATABASE_URL']\n  config.audit_database_url = ENV['AUDIT_DATABASE_URL']\n\n  # Database adapter (postgresql, mysql, mariadb)\n  config.adapter = :postgresql\n\n  # PostgreSQL-specific settings\n  config.publication_name = 'whodunit_chronicles'\n  config.replication_slot_name = 'whodunit_chronicles_slot'\n\n  # Performance tuning\n  config.batch_size = 1000\n  config.max_retry_attempts = 5\n  config.retry_delay = 10\n\n  # Table filtering\n  config.include_tables = %w[users posts comments]\n  config.exclude_tables = %w[sessions temp_data]\n  config.include_schemas = %w[public app]\n  config.exclude_schemas = %w[information_schema pg_catalog]\nend\n```\n\n## 📊 Audit Records\n\nChronicles creates structured audit records for each database change:\n\n```ruby\n{\n  id: 123,\n  table_name: \"users\",\n  schema_name: \"public\",\n  record_id: { \"id\" =\u003e 456 },\n  action: \"UPDATE\",\n  old_data: { \"id\" =\u003e 456, \"email\" =\u003e \"old@example.com\", \"name\" =\u003e \"Old Name\" },\n  new_data: { \"id\" =\u003e 456, \"email\" =\u003e \"new@example.com\", \"name\" =\u003e \"New Name\" },\n  changes: { \"email\" =\u003e [\"old@example.com\", \"new@example.com\"] },\n  user_id: 789,           # From creator_id/updater_id/deleter_id columns\n  user_type: \"User\",\n  transaction_id: \"tx_abc123\",\n  sequence_number: 42,\n  occurred_at: 2025-01-21 10:30:00 UTC,\n  created_at: 2025-01-21 10:30:01 UTC,\n  metadata: {\n    table_schema: \"public\",\n    qualified_table_name: \"public.users\",\n    changed_columns: [\"email\"],\n    chronicles_version: \"0.1.0\"\n  }\n}\n```\n\n## 🔧 Advanced Usage\n\n### Custom Processors for Analytics \u0026 Monitoring\n\n**The real power of Chronicles** comes from creating custom processors tailored for your specific analytics needs. While Whodunit captures basic \"who changed what,\" Chronicles lets you build sophisticated data pipelines for tools like **Grafana**, **DataDog**, or **Elasticsearch**.\n\nTransform database changes into actionable business intelligence with features like:\n\n- **25+ Custom Metrics**: Track business KPIs like conversion rates, time-to-hire, and cost-per-acquisition\n- **Real-time Dashboards**: Stream data to Grafana for executive reporting and operational monitoring\n- **Smart Alerting**: Trigger notifications based on business rules and thresholds\n- **Multi-destination Streaming**: Send data simultaneously to multiple analytics platforms\n\n#### Analytics-Focused Processor\n\n```ruby\nclass AnalyticsProcessor \u003c Whodunit::Chronicles::Processor\n  def build_chronicles_record(change_event)\n    super.tap do |record|\n      # Add business metrics\n      record[:business_impact] = calculate_business_impact(change_event)\n      record[:user_segment] = determine_user_segment(change_event)\n      record[:feature_flag] = current_feature_flags\n\n      # Add performance metrics\n      record[:change_size] = calculate_change_size(change_event)\n      record[:peak_hours] = during_peak_hours?\n      record[:geographic_region] = user_region(change_event)\n\n      # Add time-series friendly fields for Grafana\n      record[:hour_of_day] = Time.current.hour\n      record[:day_of_week] = Time.current.wday\n      record[:is_weekend] = weekend?\n\n      # Custom tagging for filtering\n      record[:tags] = generate_tags(change_event)\n    end\n  end\n\n  private\n\n  def calculate_business_impact(change_event)\n    case change_event.table_name\n    when 'orders' then 'revenue_critical'\n    when 'users' then 'customer_critical'\n    when 'products' then 'inventory_critical'\n    else 'standard'\n    end\n  end\n\n  def determine_user_segment(change_event)\n    return 'anonymous' unless change_event.user_id\n\n    # Look up user tier from your business logic\n    User.find(change_event.user_id)\u0026.tier || 'standard'\n  end\n\n  def generate_tags(change_event)\n    tags = [change_event.action.downcase]\n    tags \u003c\u003c 'bulk_operation' if bulk_operation?(change_event)\n    tags \u003c\u003c 'api_driven' if api_request?\n    tags \u003c\u003c 'admin_action' if admin_user?(change_event.user_id)\n    tags\n  end\nend\n```\n\n#### Grafana Dashboard Ready\n\n```ruby\nclass GrafanaProcessor \u003c Whodunit::Chronicles::Processor\n  def build_chronicles_record(change_event)\n    {\n      # Core metrics for Grafana time series\n      timestamp: change_event.occurred_at,\n      table_name: change_event.table_name,\n      action: change_event.action,\n\n      # Numerical metrics for graphs\n      records_affected: calculate_records_affected(change_event),\n      change_magnitude: calculate_change_magnitude(change_event),\n      user_session_duration: calculate_session_duration(change_event),\n\n      # Categorical dimensions for filtering\n      environment: Rails.env,\n      application_version: app_version,\n      database_instance: database_identifier,\n\n      # Business KPIs\n      revenue_impact: calculate_revenue_impact(change_event),\n      customer_satisfaction_risk: assess_satisfaction_risk(change_event),\n\n      # Performance indicators\n      query_duration_ms: extract_query_duration(change_event),\n      concurrent_users: current_concurrent_users,\n      system_load: current_system_load\n    }\n  end\nend\n```\n\n#### Real-Time Alerts Processor\n\n```ruby\nclass AlertingProcessor \u003c Whodunit::Chronicles::Processor\n  def process(change_event)\n    record = build_chronicles_record(change_event)\n\n    # Store the audit record\n    store_audit_record(record)\n\n    # Real-time alerting logic\n    send_alert(record) if alert_worthy?(record)\n\n    # Stream to monitoring systems\n    stream_to_datadog(record) if production?\n    stream_to_grafana(record)\n  end\n\n  private\n\n  def alert_worthy?(record)\n    # Define your alerting criteria\n    record[:business_impact] == 'revenue_critical' ||\n    record[:records_affected] \u003e 1000 ||\n    record[:action] == 'DELETE' \u0026\u0026 record[:table_name] == 'orders'\n  end\n\n  def stream_to_grafana(record)\n    # Send metrics to Grafana via InfluxDB/Prometheus\n    InfluxDB.write_point(\"chronicles_events\", record)\n  end\nend\n```\n\n#### Multiple Processor Pipeline\n\n```ruby\n# Chain multiple processors for different purposes\nservice = Whodunit::Chronicles::Service.new(\n  adapter: Adapters::PostgreSQL.new,\n  processor: Whodunit::Chronicles::CompositeProcessor.new([\n    AnalyticsProcessor.new,     # For business intelligence\n    AlertingProcessor.new,      # For real-time monitoring\n    ComplianceProcessor.new,    # For regulatory requirements\n    ArchivalProcessor.new       # For long-term storage\n  ])\n)\n```\n\n**Use Cases:**\n\n- **📊 Business Intelligence**: Track user behavior patterns, feature adoption, revenue impact\n- **🚨 Real-Time Monitoring**: Alert on suspicious activities, bulk operations, data anomalies\n- **📈 Performance Analytics**: Database performance metrics, query optimization insights\n- **🔍 Compliance Auditing**: Regulatory compliance, data governance, access patterns\n- **💡 Product Analytics**: Feature usage, A/B testing data, user journey tracking\n\n### Service Monitoring\n\n```ruby\nservice = Whodunit::Chronicles.service\n\n# Check service status\nstatus = service.status\nputs \"Running: #{status[:running]}\"\nputs \"Adapter Position: #{status[:adapter_position]}\"\nputs \"Retry Count: #{status[:retry_count]}\"\nputs \"Active Threads: #{status[:executor_status][:active_count]}\"\n\n# Monitor in production\nThread.new do\n  loop do\n    status = service.status\n    Rails.logger.info \"Chronicles Status: #{status}\"\n    sleep 60\n  end\nend\n```\n\n## 🧪 Testing\n\n### Integration Testing\n\nTest Chronicles with your Rails application using these patterns:\n\n#### Basic Testing Pattern\n\n```ruby\n# Test basic Chronicles functionality\nclass ChroniclesIntegrationTest \u003c ActiveSupport::TestCase\n  def setup\n    @service = Whodunit::Chronicles.service\n    @service.setup!\n    @service.start\n  end\n\n  def teardown\n    @service.stop\n    @service.teardown!\n  end\n\n  def test_audit_record_creation\n    # Create a user (triggers Whodunit)\n    user = User.create!(name: \"John\", email: \"john@example.com\")\n\n    # Wait for Chronicles to process\n    sleep 1\n\n    # Check Chronicles audit record\n    audit_record = AuditRecord.find_by(\n      table_name: 'users',\n      action: 'INSERT',\n      record_id: { 'id' =\u003e user.id }\n    )\n\n    assert audit_record\n    assert_equal 'INSERT', audit_record.action\n    assert_equal user.name, audit_record.new_data['name']\n  end\nend\n```\n\n#### Advanced Analytics Testing\n\n```ruby\n# Test custom processor functionality\nclass RecruitmentAnalyticsTest \u003c ActiveSupport::TestCase\n  def setup\n    @processor = RecruitmentAnalyticsProcessor.new\n  end\n\n  def test_recruitment_stage_determination\n    change_event = create_change_event(\n      table_name: 'applications',\n      action: 'UPDATE',\n      new_data: { 'status' =\u003e 'hired' }\n    )\n\n    record = @processor.build_chronicles_record(change_event)\n\n    assert_equal 'hire', record[:recruitment_stage]\n    assert record[:cost_per_hire_impact]\n  end\n\n  def test_metrics_streaming\n    # Mock Prometheus and Grafana integrations\n    assert_difference 'RECRUITMENT_HIRES_TOTAL.get' do\n      @processor.stream_to_prometheus(hired_record)\n    end\n  end\nend\n```\n\n### Unit Testing\n\nChronicles includes comprehensive test coverage:\n\n```bash\n# Run test suite\nbundle exec rake test\n\n# Run with coverage\nbundle exec rake test\nopen coverage/index.html\n\n# Security scanning\nbundle exec bundler-audit check\nbundle exec brakeman\n```\n\n## 📈 Performance\n\n- **Minimal Overhead**: No Rails callback performance impact\n- **Efficient Streaming**: PostgreSQL logical replication is highly optimized\n- **Configurable Batching**: Process changes in configurable batch sizes\n- **Thread Pool**: Concurrent processing with bounded resource usage\n- **Memory Efficient**: Streaming processing without loading full datasets\n\n## 🛡️ Security\n\n- **Dependency Scanning**: Automated bundler-audit checks\n- **Code Analysis**: GitHub CodeQL integration\n- **Vulnerability Monitoring**: Weekly security scans\n- **Safe Defaults**: Secure configuration out of the box\n\n## 🤝 Contributing\n\nWe welcome contributions! Chronicles is designed to be extensible and work across different business domains.\n\n1. Fork the repository\n2. Set up your development environment:\n   ```bash\n   bundle install\n   bundle exec rake test  # Ensure tests pass\n   ```\n3. Create your feature branch (`git checkout -b feature/amazing-feature`)\n4. Make your changes with comprehensive tests\n5. Test your changes:\n   - Unit tests: `bundle exec rake test`\n   - Code style: `bundle exec rubocop`\n   - Security: `bundle exec bundler-audit check`\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request with a detailed description\n\n### Contributing Custom Processors\n\nWe especially welcome custom processors for different business domains. Consider contributing processors for:\n\n- E-commerce analytics (order tracking, inventory management)\n- Financial services (transaction monitoring, compliance reporting)\n- Healthcare (patient data tracking, regulatory compliance)\n- Education (student progress, course analytics)\n- SaaS metrics (user engagement, feature adoption)\n\n## 📋 Requirements\n\n- **Ruby**: 3.1.0 or higher\n- **PostgreSQL**: 10.0 or higher (with logical replication enabled)\n- **MySQL/MariaDB**: 5.6+ (with binary logging enabled)\n\n## 🗺️ Roadmap\n\n- [ ] **Prometheus Metrics**: Production monitoring integration (with complete codebase included in examples/)\n- [ ] **Advanced Example Apps**: Real-world use cases with complete monitoring stack (with complete codebase included in examples/)\n- [ ] **Custom Analytics Processors**: Business intelligence and real-time monitoring (with complete codebase included in examples/)\n- [x] **MySQL/MariaDB Support**: MySQL/MariaDB databases binlog streaming adapter\n- [ ] **Redis Streams**: Alternative lightweight streaming backend\n- [ ] **Compression**: Optional audit record compression\n- [ ] **Retention Policies**: Automated audit record cleanup\n- [ ] **Web UI**: Management interface for monitoring and configuration\n\n## 📚 Documentation\n\n- **[API Documentation](https://kanutocd.github.io/whodunit-chronicles/)**\n- [ ] TODO: **Configuration Guide** _(docs/configuration-todo.md)_\n- [ ] TODO: **Architecture Deep Dive** _(docs/architecture-todo.md)_\n- [ ] TODO: **PostgreSQL Setup** _(docs/postgresql-setup-todo.md)_\n- [ ] TODO: **MySQL/MariaDB Setup** _(docs/mysql-setup.md)_\n- [ ] TODO: **Production Deployment** _(docs/production-todo.md)_\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- **PostgreSQL Team**: For excellent logical replication functionality\n- **Ruby Community**: For amazing gems and tools that make this possible\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**[⭐ Star us on GitHub](https://github.com/kanutocd/whodunit-chronicles)** • **[🐛 Report Bug](https://github.com/kanutocd/whodunit-chronicles/issues)** • **[💡 Request Feature](https://github.com/kanutocd/whodunit-chronicles/issues)**\n\nMade with ❤️ by a Spherical Cow\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanutocd%2Fwhodunnit-chronicles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkanutocd%2Fwhodunnit-chronicles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanutocd%2Fwhodunnit-chronicles/lists"}