# MW-Core — CloudI Integration Enhancement Requirements

**Document ID:** MW-CORE-REQ-003  
**Prepared by:** Platform Engineering — Architecture Team  
**Date:** April 27, 2026  
**Version:** 1.0  
**Status:** Approved — Ready for Development  
**Parent System:** MW-Core Middleware Platform (MomentPay TMS)  

---

## Table of Contents

1. [Purpose & Vision](#1-purpose--vision)
2. [Background & Decision Context](#2-background--decision-context)
3. [Architecture Decision Record](#3-architecture-decision-record)
4. [Hybrid Architecture Overview](#4-hybrid-architecture-overview)
5. [The Integration Decision Rule](#5-the-integration-decision-rule)
6. [What Changes in MW-Core](#6-what-changes-in-mw-core)
7. [New Umbrella Apps to Build](#7-new-umbrella-apps-to-build)
8. [The CloudI Bootstrap App](#8-the-cloudi-bootstrap-app)
9. [The CloudI Adapter App](#9-the-cloudi-adapter-app)
10. [External Service Templates](#10-external-service-templates)
11. [Functional Requirements](#11-functional-requirements)
12. [Non-Functional Requirements](#12-non-functional-requirements)
13. [Security Requirements](#13-security-requirements)
14. [Observability Requirements](#14-observability-requirements)
15. [Migration Sequence](#15-migration-sequence)
16. [Team Structure & Ownership](#16-team-structure--ownership)
17. [Acceptance Criteria](#17-acceptance-criteria)
18. [Out of Scope](#18-out-of-scope)
19. [Open Questions](#19-open-questions)
20. [Appendix](#20-appendix)

---

## 1. Purpose & Vision

### 1.1 What This Document Is

This document defines the engineering requirements to enhance MW-Core with CloudI integration, giving MomentPay's Transaction Management System a **Hybrid Integration Platform** — one that combines the strengths of both frameworks into a single, coherent architecture.

This is not a migration document. MW-Core is not being replaced. This document defines how CloudI is added to MW-Core as a supervised integration layer for external vendor systems, while MW-Core continues to own all core business logic, compliance, and internal adapter development.

### 1.2 The Vision in One Sentence

> MW-Core owns everything MomentPay writes. CloudI supervises everything the outside world brings.

### 1.3 Why This Matters for Digital Transformation

MomentPay is executing a Digital Transformation programme. This means connecting to systems acquired from multiple software vendors — each potentially built in a different programming language, using a different protocol, and maintained by a different team. The vendor landscape is unknown today but will grow over time.

MW-Core's existing adapter model — where every adapter is written in Elixir by MomentPay's team — works perfectly for integrations the team controls. It does not scale for integrations where a vendor delivers a Java SDK, a Python service, a C++ engine, or a legacy COBOL batch system. Requiring the team to rewrite every vendor integration in Elixir creates maintenance burden, ownership confusion, and version drift every time a vendor releases an update.

CloudI solves this by making non-Elixir vendor services **first-class citizens** of the platform — fully supervised by OTP, fault-tolerant, communicating via a language-agnostic message bus, and invisible to MW-Core's core plane beyond a service name.

---

## 2. Background & Decision Context

### 2.1 What MW-Core Already Has (Do Not Break These)

| Capability | Status | Owner |
|---|---|---|
| REST API Gateway (`gateway_api`) | Production ready | Platform Engineering |
| WebSocket Gateway (`gateway_ws`) | Production ready | Platform Engineering |
| Admin Dashboard (`gateway_web` / LiveView) | Production ready | Platform Engineering |
| Mobile Gateway (`gateway_mobile`) | Production ready | Platform Engineering |
| Core Plane: auth, routing, transform, audit | Production ready | Platform Engineering |
| Core Banking Adapter (`adapter_banking`) | Production ready | Platform Engineering |
| File Ingestion Pipeline (`adapter_file`) | Production ready | Platform Engineering |
| Data Warehouse Adapter (`adapter_dw`) | Production ready | Platform Engineering |
| Multi-node clustering (libcluster + Horde) | Production ready | Platform Engineering |
| Observability (OTel + Prometheus) | Production ready | Platform Engineering |

**None of the above changes as part of this enhancement.** This document only adds capabilities.

### 2.2 The Gap This Enhancement Fills

MW-Core has no clean, supervised mechanism to integrate with:

- Vendor-supplied SDKs in Java, Python, Go, Rust, or other languages
- Legacy systems that cannot be wrapped in a simple HTTP API
- Third-party processing engines that must run as native OS processes
- Future acquired systems whose language and protocol are not yet known

Without CloudI, each of these requires either a bespoke HTTP wrapper service (separate deployment, separate auth, separate observability) or a dangerous NIF/port integration inside the BEAM VM. CloudI provides a third option: a supervised external service that communicates via the CloudI message bus with full OTP fault tolerance, at the cost of neither stability nor ownership clarity.

### 2.3 What Was Considered and Why This Was Chosen

Three options were evaluated:

**Option A — HTTP wrapper per vendor:** Each vendor integration becomes a separate microservice with an HTTP API. MW-Core calls it via `adapter_http`. This works but requires each wrapper to have its own deployment pipeline, health checks, auth, and observability. Operational burden scales with vendor count.

**Option B — Full CloudI migration:** Replace MW-Core entirely with CloudI as the platform foundation. Rejected because MW-Core's Phoenix ecosystem provides capabilities CloudI cannot match — particularly Phoenix LiveView for the admin dashboard, Phoenix Channels for WebSocket push, and the mature Elixir testing and observability toolchain.

**Option C — Hybrid: MW-Core owns internal, CloudI owns external (CHOSEN):** MW-Core continues as the core platform. CloudI is added as a supervised integration layer exclusively for external vendor services. A single `AdapterCloudi` in MW-Core makes all CloudI services look identical to native Elixir adapters to the core plane. This is the chosen approach.

---

## 3. Architecture Decision Record

| Field | Value |
|---|---|
| **Decision** | Adopt CloudI as the external vendor integration layer within the MW-Core umbrella |
| **Status** | Approved |
| **Deciders** | Engineering Leadership, Platform Architecture |
| **Date** | April 27, 2026 |
| **Consequence** | MW-Core umbrella gains CloudI bootstrap and adapter apps. All future vendor integrations in non-Elixir languages are delivered as CloudI external services. All Elixir-native integrations continue as MW-Core adapter apps. |
| **Reversibility** | High — CloudI apps can be removed from the umbrella without affecting MW-Core's core plane |

---

## 4. Hybrid Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────┐
│                     EXTERNAL CLIENTS                                │
│   REST / WebSocket / Browser / Mobile App                          │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────────────┐
│               NORTH PLANE — Gateways (UNCHANGED)                    │
│   gateway_api │ gateway_ws │ gateway_web │ gateway_mobile           │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────────────┐
│               CORE PLANE — Processing (UNCHANGED)                   │
│   mw_auth → mw_router → mw_transform → mw_audit                    │
│                    │                                                │
│              mw_kernel (shared contracts)                           │
└──────────┬──────────────────────────────┬───────────────────────────┘
           │                              │
           ▼                              ▼
┌──────────────────────┐    ┌─────────────────────────────────────────┐
│  SOUTH PLANE         │    │  CLOUDI INTEGRATION LAYER (NEW)         │
│  MW-Core Adapters    │    │                                         │
│  (Elixir, team owns) │    │  cloudi_bootstrap  ← CloudI runtime     │
│                      │    │  adapter_cloudi    ← bridge to MW-Core  │
│  adapter_banking     │    │                                         │
│  adapter_dw          │    │  ┌─────────────────────────────────┐    │
│  adapter_http        │    │  │  CloudI External Services        │    │
│  adapter_file        │    │  │  (vendor language, CloudI owns)  │    │
│  [future Elixir      │    │  │                                  │    │
│   adapters]          │    │  │  Java vendor SDK service         │    │
│                      │    │  │  Python fraud / ML engine        │    │
│  Decision rule:      │    │  │  Python file parser (pandas)     │    │
│  "We wrote it"       │    │  │  Legacy C++ processor            │    │
│                      │    │  │  [any future vendor language]    │    │
└──────────┬───────────┘    │  │                                  │    │
           │                │  │  Decision rule:                  │    │
           │                │  │  "They wrote it"                 │    │
           │                │  └─────────────────────────────────┘    │
           │                └──────────────────┬──────────────────────┘
           │                                   │
           └─────────────┬─────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────────────────┐
│               INFRA PLANE — Shared Services (UNCHANGED)             │
│   infra_repo │ infra_cache │ infra_queue │ infra_telemetry          │
└─────────────────────────────────────────────────────────────────────┘
                         │
         ┌───────────────┼───────────────────────────┐
         ▼               ▼                           ▼
   Core Banking    Data Warehouse         Vendor Systems
   (ISO 8583)      (ETL/Batch)            (any language, any protocol)
```

### 4.1 The Key Insight

To `mw_router`, there is **no difference** between calling `AdapterBanking.send/2` (pure Elixir) and calling `AdapterCloudi.send/2` (which dispatches to a Java process via CloudI). Both implement `MwKernel.Adapter`. Both return `{:ok, %MwKernel.Message{}}` or `{:error, %MwKernel.Error{}}`. The core plane is completely shielded from knowing that CloudI exists.

---

## 5. The Integration Decision Rule

This rule governs every future integration decision. It must be applied consistently by all teams. When a new integration requirement arrives, follow this decision tree:

```
New integration requirement arrives
              │
              ▼
        Question 1:
   Is the integration logic
   written by our team, OR
   will we write it from scratch
   in Elixir?
              │
       ┌──────┴──────┐
      YES             NO
       │               │
       ▼               ▼
  Write Elixir    Question 2:
  adapter app     Does the vendor supply
  in MW-Core.     a native SDK, library,
  Follow existing or service in a specific
  adapter_banking  programming language?
  pattern.              │
                  ┌─────┴─────┐
                 YES           NO
                  │             │
                  ▼             ▼
           Wrap as CloudI   Question 3:
           external service  Can we write a
           in vendor's       thin Elixir HTTP
           language.         wrapper cleanly?
           cloudi_[name]          │
           app in umbrella.  ┌────┴────┐
                            YES        NO
                             │          │
                             ▼          ▼
                       adapter_http  CloudI external
                       in MW-Core    service anyway
```

### 5.1 Examples Applying the Rule

| Scenario | Decision | Rationale |
|---|---|---|
| Write a new adapter for a REST banking API | Elixir adapter in MW-Core | We write the code, we own it |
| Vendor delivers ISO 8583 Java SDK jar | CloudI external service in Java | Vendor wrote it, vendor maintains it |
| Python fraud detection model from data science team | CloudI external service in Python | Different team, Python ecosystem needed |
| New SFTP partner with same CSV format as existing | Elixir adapter extends adapter_file | Same pattern, our code |
| Vendor delivers C++ real-time pricing engine | CloudI external service in C++ | Binary, cannot rewrite |
| Internal SOAP legacy system we do not own | adapter_http in MW-Core | Thin HTTP wrapper is clean enough |
| Legacy COBOL batch system, no HTTP possible | CloudI external service | No HTTP, must use CloudI bridge |

---

## 6. What Changes in MW-Core

### 6.1 The Core Plane Does Not Change

`mw_auth`, `mw_router`, `mw_transform`, `mw_audit`, and `mw_kernel` are **not modified** as part of this enhancement. The `MwKernel.Adapter` behaviour contract is the integration seam and it already exists. No new behaviour callbacks, no new message types, no pipeline stage changes.

### 6.2 The Route Table Gains a New Adapter Type

The ETS routing table currently maps `{tenant_id, message_type}` to an adapter module. CloudI-backed integrations use `AdapterCloudi` as their adapter module. The route table entry format does not change — only the value of the adapter field changes for CloudI-backed routes.

```
# Route entry for a native Elixir adapter (existing pattern)
{{"tenant_a", :transaction_create}, AdapterBanking}

# Route entry for a CloudI-backed vendor service (new pattern)
{{"tenant_a", :fraud_check}, AdapterCloudi}
{{"tenant_a", :pricing_engine}, AdapterCloudi}
```

The admin UI route editor needs one addition: when creating a route, operators can select `CloudI Service` as the adapter type and enter the CloudI service name. No other UI change is needed.

### 6.3 Umbrella Root mix.exs

Add the two new apps to the umbrella:

```elixir
# mix.exs (umbrella root) — add these two entries
defp apps do
  [
    :mw_kernel,
    :mw_auth,
    :mw_router,
    :mw_transform,
    :mw_audit,
    :gateway_api,
    :gateway_ws,
    :gateway_web,
    :gateway_mobile,
    :adapter_banking,
    :adapter_dw,
    :adapter_http,
    :adapter_file,
    :infra_repo,
    :infra_cache,
    :infra_queue,
    :infra_telemetry,
    :cloudi_bootstrap,    # NEW — CloudI runtime supervisor
    :adapter_cloudi       # NEW — CloudI bridge adapter
  ]
end
```

---

## 7. New Umbrella Apps to Build

Two new apps are required. All future vendor-specific CloudI service apps are separate from the umbrella (they live in `services/` as standalone CloudI external service projects).

| App | Type | Purpose |
|---|---|---|
| `cloudi_bootstrap` | Elixir umbrella app | Starts and supervises the CloudI runtime inside the BEAM VM |
| `adapter_cloudi` | Elixir umbrella app | Implements `MwKernel.Adapter`, bridges MW-Core to CloudI message bus |

All external vendor services (Java, Python, etc.) live in `services/[vendor_name]/` at the umbrella root — **not** inside `apps/`. They are separate projects in separate languages, registered with CloudI at runtime.

```
mw_core/
  apps/
    ...existing apps...
    cloudi_bootstrap/     ← NEW: Elixir umbrella app
    adapter_cloudi/       ← NEW: Elixir umbrella app
  services/
    file_parser_py/       ← Python CloudI external service (example)
    banking_sdk_java/     ← Java CloudI external service (example)
    fraud_engine_py/      ← Python ML service (example)
  config/
    cloudi.conf           ← CloudI service registry configuration
```

---

## 8. The CloudI Bootstrap App

### 8.1 Purpose

`cloudi_bootstrap` is responsible for starting the CloudI runtime as part of the umbrella supervision tree. It must start **after** `infra_repo` and `infra_cache` but **before** `adapter_cloudi`. It registers all CloudI services defined in `config/cloudi.conf` at startup.

### 8.2 App Structure

```
apps/cloudi_bootstrap/
  lib/
    cloudi_bootstrap/
      application.ex      ← starts CloudI supervisor
      service_registry.ex ← loads and registers services from config
      health_check.ex     ← reports CloudI health to /health/ready
  mix.exs
  config/
    config.exs
```

### 8.3 Functional Requirements

| ID | Requirement |
|---|---|
| FR-CB-01 | `cloudi_bootstrap` must start the CloudI supervisor as a child of the umbrella application supervisor |
| FR-CB-02 | CloudI must start after `infra_repo` and `infra_cache` are confirmed healthy |
| FR-CB-03 | CloudI service registration must load service definitions from `config/cloudi.conf` |
| FR-CB-04 | If CloudI fails to start, the umbrella application must continue running with MW-Core fully operational — CloudI is additive, not load-bearing for existing functionality |
| FR-CB-05 | CloudI service health must be exposed to `GET /health/ready` — if CloudI is unhealthy, only CloudI-backed routes return 503; MW-Core native routes are unaffected |
| FR-CB-06 | Services must be registerable and deregisterable at runtime without restarting the umbrella |
| FR-CB-07 | `cloudi_bootstrap` must emit a telemetry event on service registration, deregistration, and failure |

### 8.4 Reference Implementation

```elixir
# apps/cloudi_bootstrap/lib/cloudi_bootstrap/application.ex
defmodule CloudiBootstrap.Application do
  use Application
  require Logger

  def start(_type, _args) do
    children = [
      {CloudiBootstrap.ServiceRegistry, []},
      {CloudiBootstrap.HealthCheck, []}
    ]

    case Supervisor.start_link(children,
           strategy: :one_for_one,
           name: CloudiBootstrap.Supervisor) do
      {:ok, pid} ->
        Logger.info("[CloudI] Bootstrap supervisor started")
        {:ok, pid}
      {:error, reason} ->
        # CloudI failure must not crash the umbrella
        Logger.error("[CloudI] Bootstrap failed: #{inspect(reason)}")
        Logger.warning("[CloudI] MW-Core continues without CloudI integration")
        # Start a minimal supervisor so the app is still "started"
        Supervisor.start_link([], strategy: :one_for_one,
                               name: CloudiBootstrap.Supervisor)
    end
  end
end
```

```elixir
# apps/cloudi_bootstrap/lib/cloudi_bootstrap/service_registry.ex
defmodule CloudiBootstrap.ServiceRegistry do
  use GenServer
  require Logger

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(_) do
    services = Application.get_env(:cloudi_bootstrap, :services, [])
    Enum.each(services, &register_service/1)
    {:ok, %{services: services}}
  end

  def register_service(%{name: name, module: module} = service) do
    case :cloudi.services_add([service], :infinity) do
      {:ok, _ids} ->
        Logger.info("[CloudI] Service registered: #{name}")
        :telemetry.execute([:cloudi, :service, :registered],
                           %{count: 1}, %{service: name})
      {:error, reason} ->
        Logger.error("[CloudI] Failed to register #{name}: #{inspect(reason)}")
    end
  end
end
```

---

## 9. The CloudI Adapter App

### 9.1 Purpose

`adapter_cloudi` is the **single integration point** between MW-Core's core plane and all CloudI external services. It implements `MwKernel.Adapter` exactly like `adapter_banking` or `adapter_http`. The core plane calls it identically — it has no knowledge that CloudI exists.

### 9.2 App Structure

```
apps/adapter_cloudi/
  lib/
    adapter_cloudi/
      adapter.ex          ← implements MwKernel.Adapter behaviour
      dispatcher.ex       ← sends/receives CloudI messages
      encoder.ex          ← MwKernel.Message → CloudI payload
      decoder.ex          ← CloudI response → MwKernel.Message
      circuit_breaker.ex  ← :fuse integration for CloudI services
      service_resolver.ex ← resolves message type → CloudI service name
  test/
  mix.exs
```

### 9.3 Functional Requirements

| ID | Requirement |
|---|---|
| FR-AC-01 | `adapter_cloudi` must implement `MwKernel.Adapter` behaviour — same callbacks as every other adapter |
| FR-AC-02 | `AdapterCloudi.send/2` must accept `%MwKernel.Message{}` and return `{:ok, %MwKernel.Message{}}` or `{:error, %MwKernel.Error{}}` |
| FR-AC-03 | Service name resolution must follow the pattern `/{tenant_id}/{message_type_path}` |
| FR-AC-04 | `adapter_cloudi` must have its own `:fuse` circuit breaker per CloudI service name — a failing Java service must not affect a healthy Python service |
| FR-AC-05 | Timeouts must be configurable per service name in application config |
| FR-AC-06 | `adapter_cloudi` must propagate `trace_id` and `tenant_id` from the MW-Core context into the CloudI request metadata |
| FR-AC-07 | All CloudI calls must emit `:telemetry` events with the same shape as native adapter telemetry events |
| FR-AC-08 | If CloudI is not running (bootstrap failed), `adapter_cloudi` must return `{:error, %MwKernel.Error{reason: :cloudi_unavailable}}` — not raise an exception |
| FR-AC-09 | `adapter_cloudi` must support both synchronous (`send_sync`) and asynchronous (`send_async`) CloudI calls, configured per service name |

### 9.4 Reference Implementation

```elixir
# apps/adapter_cloudi/lib/adapter_cloudi/adapter.ex
defmodule AdapterCloudi do
  @behaviour MwKernel.Adapter
  require Logger

  alias AdapterCloudi.{Dispatcher, Encoder, Decoder,
                        ServiceResolver, CircuitBreaker}

  @impl MwKernel.Adapter
  def send(state, %MwKernel.Message{} = message) do
    service_name = ServiceResolver.resolve(
      message.type,
      message.context.tenant_id
    )

    start_time = System.monotonic_time()

    result =
      with :ok <- CircuitBreaker.check(service_name),
           {:ok, payload} <- Encoder.encode(message),
           {:ok, response} <- Dispatcher.send_sync(service_name, payload,
                                timeout: timeout_for(service_name)),
           {:ok, decoded} <- Decoder.decode(response) do
        {:ok, decoded}
      else
        {:error, :circuit_open} ->
          {:error, %MwKernel.Error{
            reason: :circuit_open,
            adapter: :cloudi,
            service: service_name
          }}
        {:error, :timeout} ->
          :fuse.melt(service_name)
          {:error, %MwKernel.Error{
            reason: :timeout,
            adapter: :cloudi,
            service: service_name
          }}
        {:error, reason} ->
          {:error, %MwKernel.Error{
            reason: reason,
            adapter: :cloudi,
            service: service_name
          }}
      end

    duration = System.monotonic_time() - start_time

    :telemetry.execute(
      [:mw, :adapter_cloudi, :call],
      %{duration: duration},
      %{
        service: service_name,
        tenant_id: message.context.tenant_id,
        trace_id: message.context.trace_id,
        status: elem(result, 0)
      }
    )

    result
  end

  defp timeout_for(service_name) do
    Application.get_env(:adapter_cloudi, :timeouts, %{})
    |> Map.get(service_name, 5_000)
  end
end
```

```elixir
# apps/adapter_cloudi/lib/adapter_cloudi/service_resolver.ex
defmodule AdapterCloudi.ServiceResolver do

  @doc """
  Resolves a MW-Core message type and tenant_id to a
  CloudI service name.

  Examples:
    resolve(:fraud_check, "tenant_a") → "/tenant_a/fraud/check"
    resolve(:pricing_engine, "tenant_b") → "/tenant_b/pricing/engine"
  """
  def resolve(message_type, tenant_id) do
    path = message_type
           |> Atom.to_string()
           |> String.replace("_", "/")

    "/#{tenant_id}/#{path}"
  end
end
```

---

## 10. External Service Templates

These templates are the reference implementation for every vendor integration delivered as a CloudI external service. Developers must follow the template for their vendor's language. Templates ensure consistent structure, error handling, logging, and observability across all vendor services.

### 10.1 Python External Service Template

Use for: fraud detection, ML inference, complex CSV/XML parsing, data science workloads.

```python
# services/[service_name]/main.py
import sys
import json
import logging
sys.path.append('/usr/local/lib/cloudi-2.0.7/api/python/')

from cloudi import API, terminate_exception

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] [CloudI:%(name)s] %(message)s'
)
logger = logging.getLogger('[SERVICE_NAME]')


class [ServiceName]Service:
    """
    CloudI external service template — Python.
    Replace [SERVICE_NAME] and [ServiceName] with actual service name.
    Subscribe path must match the route registered in cloudi.conf.
    """

    def __init__(self):
        self.__api = API(0)

    def run(self):
        try:
            # Subscribe path must match ServiceResolver output in adapter_cloudi
            self.__api.subscribe("[tenant_pattern]/[service_path]",
                                 self.__handle_request)
            logger.info("Service started, waiting for requests")
            self.__api.poll()
        except terminate_exception:
            logger.info("Service terminated cleanly")
        except Exception as e:
            logger.error(f"Service error: {e}", exc_info=True)

    def __handle_request(self, request_type, name, pattern,
                          request_info, request,
                          timeout, priority, trans_id, pid):
        try:
            # Parse incoming MW-Core message (JSON encoded by adapter_cloudi)
            payload = json.loads(request)
            trace_id = json.loads(request_info).get('trace_id', 'unknown')

            logger.info(f"Processing request trace_id={trace_id}")

            # --- YOUR VENDOR LOGIC HERE ---
            result = self.__process(payload)
            # ------------------------------

            logger.info(f"Request complete trace_id={trace_id}")
            return json.dumps({"status": "ok", "data": result})

        except Exception as e:
            logger.error(f"Request failed: {e}", exc_info=True)
            return json.dumps({"status": "error", "reason": str(e)})

    def __process(self, payload):
        # Implement vendor-specific logic here
        # Full Python ecosystem available: pandas, numpy, scikit-learn,
        # lxml, pydantic, etc.
        raise NotImplementedError("Implement vendor logic here")


if __name__ == '__main__':
    assert API.thread_count() == 1, "Single-threaded service required"
    [ServiceName]Service().run()
```

```
# services/[service_name]/requirements.txt
# Pin all versions — vendor services must be reproducible
cloudi==2.0.7
# Add vendor-specific dependencies below:
# pandas==2.2.0
# pydantic==2.6.0
# lxml==5.1.0
```

```
# services/[service_name]/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
```

### 10.2 Java External Service Template

Use for: vendor banking SDKs, ISO 8583 Java libraries, enterprise integration adapters.

```java
// services/[service_name]/src/main/java/[ServiceName]Service.java
import org.cloudi.API;
import org.json.JSONObject;
import java.util.logging.Logger;

public class [ServiceName]Service {

    private static final Logger logger =
        Logger.getLogger([ServiceName]Service.class.getName());

    private final API api;

    public [ServiceName]Service(int threadIndex) throws Exception {
        this.api = new API(threadIndex);
    }

    public void run() {
        try {
            // Subscribe path must match ServiceResolver output
            this.api.subscribe("[tenant_pattern]/[service_path]",
                               this, "handleRequest");
            logger.info("Service started, waiting for requests");
            this.api.poll();
        } catch (API.TerminateException e) {
            logger.info("Service terminated cleanly");
        } catch (Exception e) {
            logger.severe("Service error: " + e.getMessage());
        }
    }

    public Object handleRequest(
        Integer requestType, String name, String pattern,
        byte[] requestInfo, byte[] request,
        Integer timeout, Byte priority,
        byte[] transId, com.ericsson.otp.erlang.OtpErlangPid pid
    ) {
        try {
            JSONObject payload = new JSONObject(new String(request));
            JSONObject info = new JSONObject(new String(requestInfo));
            String traceId = info.optString("trace_id", "unknown");

            logger.info("Processing request trace_id=" + traceId);

            // --- YOUR VENDOR SDK LOGIC HERE ---
            Object result = process(payload);
            // ----------------------------------

            JSONObject response = new JSONObject();
            response.put("status", "ok");
            response.put("data", result);
            return response.toString().getBytes();

        } catch (Exception e) {
            logger.severe("Request failed: " + e.getMessage());
            JSONObject error = new JSONObject();
            error.put("status", "error");
            error.put("reason", e.getMessage());
            return error.toString().getBytes();
        }
    }

    private Object process(JSONObject payload) throws Exception {
        // Implement vendor SDK logic here
        // Full Java ecosystem available: vendor JARs, Spring, etc.
        throw new UnsupportedOperationException("Implement vendor logic");
    }

    public static void main(String[] args) throws Exception {
        assert API.thread_count() == 1;
        new [ServiceName]Service(0).run();
    }
}
```

### 10.3 CloudI Service Configuration Template

Every external service, regardless of language, must have a corresponding entry in `config/cloudi.conf`:

```erlang
%% config/cloudi.conf
%% Add one entry per external service.
%% prefix must match the ServiceResolver pattern in adapter_cloudi.

[
  %% Python fraud detection service
  [{prefix,     "/+/fraud/"},          %% "+" matches any tenant_id
   {file_path,  "/app/services/fraud_engine_py/venv/bin/python"},
   {args,       "/app/services/fraud_engine_py/main.py"},
   {count_process, 2},                 %% run 2 instances for load distribution
   {max_r,      5},                    %% max 5 restarts
   {max_t,      60},                   %% within 60 seconds
   {env,        [{"PYTHONPATH", "/usr/local/lib/cloudi-2.0.7/api/python"}]}
  ],

  %% Java banking SDK service
  [{prefix,     "/+/banking_sdk/"},
   {file_path,  "/usr/bin/java"},
   {args,       "-cp /app/services/banking_sdk_java/target/banking-sdk.jar"
                " BankingSDKService"},
   {count_process, 1},
   {max_r,      3},
   {max_t,      30}
  ]
]
```

---

## 11. Functional Requirements

### 11.1 CloudI Runtime Requirements

| ID | Requirement |
|---|---|
| FR-RT-01 | CloudI must start as part of the umbrella application supervision tree |
| FR-RT-02 | CloudI startup failure must not prevent MW-Core from starting |
| FR-RT-03 | CloudI services must be registerable at runtime without a cluster restart |
| FR-RT-04 | Each external service must run under CloudI's MaxR/MaxT fault tolerance constraints, configurable per service |
| FR-RT-05 | A crashed external service must be restarted automatically by CloudI within its MaxR/MaxT limits |
| FR-RT-06 | If a service exceeds MaxR/MaxT, CloudI must deregister it and emit a telemetry event — not crash the umbrella |
| FR-RT-07 | External services must each run in their own OS process — a memory leak or crash in one must not affect others |

### 11.2 Message Bus Requirements

| ID | Requirement |
|---|---|
| FR-MB-01 | All CloudI service names must follow the pattern `/{tenant_id}/{service_path}` to maintain tenant isolation |
| FR-MB-02 | `trace_id` from MW-Core context must be forwarded in CloudI request metadata (`request_info`) |
| FR-MB-03 | `tenant_id` from MW-Core context must be forwarded in CloudI request metadata |
| FR-MB-04 | CloudI message encoding between `adapter_cloudi` and external services must use JSON |
| FR-MB-05 | CloudI message size must be capped at 10MB per request — larger payloads must be split or streamed |
| FR-MB-06 | Request timeout per service must be configurable in application environment, defaulting to 5 seconds |

### 11.3 Admin Dashboard Requirements

| ID | Requirement |
|---|---|
| FR-AD-01 | The route editor in `gateway_web` must allow operators to select `CloudI Service` as an adapter type |
| FR-AD-02 | When `CloudI Service` is selected, operators must be able to enter the service path (e.g. `fraud/check`) |
| FR-AD-03 | The dashboard must display the health status of each registered CloudI service |
| FR-AD-04 | The dashboard must show restart counts per CloudI service since last boot |
| FR-AD-05 | The dashboard must clearly distinguish MW-Core native adapters from CloudI-backed adapters in the route table view |

### 11.4 Developer Workflow Requirements

| ID | Requirement |
|---|---|
| FR-DW-01 | A developer must be able to add a new CloudI external service by: (1) creating a `services/[name]/` directory from the language template, (2) adding a `cloudi.conf` entry, (3) registering the route in the admin UI — no changes to core plane code |
| FR-DW-02 | Each language template (Python, Java) must include a working local development setup with Docker Compose |
| FR-DW-03 | A `mix cloudi.list` task must show all registered CloudI services, their status, and their restart counts |
| FR-DW-04 | A `mix cloudi.health` task must check connectivity to all registered CloudI services and report pass/fail |

---

## 12. Non-Functional Requirements

| ID | Requirement | Target |
|---|---|---|
| NFR-01 | `adapter_cloudi` call latency overhead vs. direct Elixir adapter | < 2 ms added P99 |
| NFR-02 | CloudI external service restart time after crash | < 3 seconds |
| NFR-03 | Number of concurrent CloudI external services supported | Up to 20 services |
| NFR-04 | CloudI bootstrap time at umbrella startup | < 10 seconds |
| NFR-05 | Throughput of CloudI message bus under load | > 10,000 msg/sec |
| NFR-06 | Memory overhead of CloudI runtime within the BEAM VM | < 256 MB at idle |
| NFR-07 | MW-Core native adapter performance must be unaffected by CloudI running alongside | Zero degradation |

---

## 13. Security Requirements

| ID | Requirement |
|---|---|
| SR-01 | CloudI service names must always include `tenant_id` as the first path segment — bare service names without tenant scope are rejected |
| SR-02 | External service processes must run as a non-root OS user |
| SR-03 | Communication between the Erlang VM and external services uses CloudI's local socket — no network exposure |
| SR-04 | `trace_id` and `tenant_id` are the only MW-Core context fields forwarded to external services — JWT tokens, API keys, and user credentials must never be forwarded |
| SR-05 | External service Docker images must be built from pinned base images and scanned for CVEs before deployment |
| SR-06 | CloudI service configuration (`cloudi.conf`) must not contain secrets — all secrets are injected via environment variables at runtime |
| SR-07 | A CloudI external service must not be able to call another CloudI service directly — all inter-service calls must route through `adapter_cloudi` and MW-Core's pipeline |

---

## 14. Observability Requirements

### 14.1 New Telemetry Events

| Event | Measurements | Metadata |
|---|---|---|
| `[:cloudi, :service, :registered]` | `%{count: 1}` | `%{service: name}` |
| `[:cloudi, :service, :crashed]` | `%{restart_count: n}` | `%{service: name}` |
| `[:cloudi, :service, :exhausted]` | `%{count: 1}` | `%{service: name}` |
| `[:mw, :adapter_cloudi, :call]` | `%{duration: ns}` | `%{service:, tenant_id:, trace_id:, status:}` |
| `[:mw, :adapter_cloudi, :timeout]` | `%{count: 1}` | `%{service:, tenant_id:}` |
| `[:mw, :adapter_cloudi, :circuit_open]` | `%{count: 1}` | `%{service:, tenant_id:}` |

### 14.2 New Prometheus Metrics

| Metric | Type | Description |
|---|---|---|
| `mw_cloudi_call_duration_ms` | Histogram | End-to-end call duration per CloudI service |
| `mw_cloudi_service_restarts_total` | Counter | Restart count per service since boot |
| `mw_cloudi_service_up` | Gauge | 1 = healthy, 0 = down, per service |
| `mw_cloudi_circuit_open` | Gauge | 1 = circuit open, per service |
| `mw_cloudi_messages_total` | Counter | Total messages dispatched per service |

### 14.3 OTel Span Requirements

`adapter_cloudi` must create a child span for every CloudI call. The span must include:

```
cloudi.service_name  = "/tenant_a/fraud/check"
cloudi.tenant_id     = "tenant_a"
cloudi.language      = "python" | "java" | "go" | ...
cloudi.sync          = true | false
trace_id             = inherited from MW-Core context
```

### 14.4 Dashboard Additions

The `gateway_web` LiveView dashboard must add a **CloudI Services** panel showing:

- Service name and language
- Status: Running / Restarting / Exhausted / Disabled
- Restart count since last boot
- Last 5 minute call volume
- P99 latency (last 5 minutes)
- Circuit breaker state

---

## 15. Migration Sequence

### Phase 0 — Foundation (Weeks 1–3)

**Goal:** CloudI running inside the umbrella. No behaviour change to existing system.

- [ ] Add `cloudi_core` hex dependency to umbrella root
- [ ] Create `apps/cloudi_bootstrap/` — start CloudI supervisor
- [ ] Create `apps/adapter_cloudi/` — implement `MwKernel.Adapter`
- [ ] Create `config/cloudi.conf` — empty service registry
- [ ] Write `mix cloudi.list` and `mix cloudi.health` mix tasks
- [ ] Add CloudI metrics to `infra_telemetry`
- [ ] Add CloudI services panel to `gateway_web` LiveView dashboard
- [ ] All existing 86 tests must continue to pass

**Acceptance:** `cloudi_bootstrap` starts cleanly. `mix cloudi.health` reports healthy. All existing routes and adapters work identically.

---

### Phase 1 — First External Service (Weeks 4–6)

**Goal:** One real CloudI external service running in production alongside MW-Core.

**Recommended first service:** Python CSV/XML parser for `adapter_file`.

- [ ] Create `services/file_parser_py/` from Python template
- [ ] Implement CSV/XML parsing with pandas and lxml
- [ ] Register in `config/cloudi.conf`
- [ ] Add route `{tenant_id, :file_parse_complex}` → `AdapterCloudi`
- [ ] `adapter_file` routes complex parsing jobs to CloudI Python service
- [ ] Simple CSV rows continue via existing Broadway pipeline (unchanged)
- [ ] Chaos test: kill Python process — verify CloudI restarts within 3 seconds
- [ ] Load test: verify P99 < 300ms maintained

**Acceptance:** Complex file parsing works via Python service. Crash recovery verified. Observability complete.

---

### Phase 2 — First Vendor SDK Service (Weeks 7–10)

**Goal:** First real vendor-supplied SDK running as a CloudI external service.

- [ ] Identify first vendor with non-HTTP SDK (Java or Python)
- [ ] Create `services/[vendor_name]/` from appropriate language template
- [ ] Implement vendor SDK logic inside CloudI service template
- [ ] Register routes for vendor message types → `AdapterCloudi`
- [ ] Circuit breaker configured for vendor service
- [ ] Timeout configured per vendor SLA
- [ ] Parallel test: same request to both vendor CloudI service and any existing HTTP wrapper — verify responses match
- [ ] Retire HTTP wrapper if results match

**Acceptance:** Vendor SDK integrated. No HTTP wrapper needed. Fault tolerance verified.

---

### Phase 3 — Ongoing (Weeks 11+)

**Goal:** Every new non-Elixir vendor integration follows the CloudI pattern. No new bespoke HTTP wrappers ever built.

- [ ] Apply integration decision rule (Section 5) to every new requirement
- [ ] Each new vendor service created from language template
- [ ] Each new route registered in admin UI
- [ ] No changes to MW-Core core plane for any new vendor integration

---

## 16. Team Structure & Ownership

| Team | Owns | Responsibilities |
|---|---|---|
| **Platform Engineering** | MW-Core core plane, all Elixir adapters, `cloudi_bootstrap`, `adapter_cloudi` | Core business logic, compliance, routing rules, admin UI |
| **Integration Engineering** | `services/[vendor_name]/` directories, `config/cloudi.conf` entries | Vendor SDK integration, external service templates, CloudI service configuration |
| **Data Engineering** | `services/file_parser_py/`, `services/fraud_engine_py/` | Python services, ML models, data pipeline logic |
| **Operations** | Kubernetes deployment, CloudI process supervision, service health | Deployment, monitoring, incident response |

### 16.1 Integration Engineering Workflow

When a new vendor integration arrives:

1. Apply the integration decision rule (Section 5)
2. If CloudI: copy the appropriate language template from `services/_templates/`
3. Implement vendor logic inside `__process()` / `process()` — do not touch the template scaffolding
4. Add entry to `config/cloudi.conf`
5. Raise a PR — Platform Engineering reviews only the `cloudi.conf` entry and route configuration, not the vendor implementation
6. Register route in admin UI after merge
7. Done — no MW-Core code review needed

---

## 17. Acceptance Criteria

### Phase 0 Complete When:

- [ ] `cloudi_bootstrap` app starts and stops cleanly with the umbrella
- [ ] CloudI startup failure does not crash MW-Core
- [ ] All 86 existing tests pass with no changes
- [ ] `mix cloudi.list` runs and reports empty service list
- [ ] CloudI panel visible in admin LiveView dashboard (empty state)
- [ ] No latency regression on existing routes (k6 baseline comparison)

### Phase 1 Complete When:

- [ ] Python file parser service registered and handling requests
- [ ] Kill Python process → service restarts within 3 seconds → requests resume
- [ ] `mw_cloudi_service_restarts_total` increments on restart
- [ ] `mw_cloudi_service_up` gauge goes 1 → 0 → 1 during crash/restart cycle
- [ ] OTel span for CloudI call visible in trace waterfall
- [ ] P99 latency for file parsing routes within 300ms budget

### Phase 2 Complete When:

- [ ] First vendor SDK service handling production message types
- [ ] Circuit breaker opens when vendor service is unresponsive
- [ ] HTTP 503 returned to client with `retry_after` when circuit is open
- [ ] Circuit closes automatically after recovery window
- [ ] Admin dashboard shows vendor service health and restart count
- [ ] No regression on any MW-Core native adapter routes

### Ongoing Acceptance:

- [ ] Every new non-Elixir vendor integration delivered as a CloudI external service — no new bespoke HTTP wrappers
- [ ] Integration decision rule applied and documented for every new requirement
- [ ] `mix cloudi.health` passes in CI pipeline

---

## 18. Out of Scope

| Item | Rationale |
|---|---|
| Replacing MW-Core with CloudI | MW-Core is the permanent platform for Elixir-owned integrations |
| Replacing Phoenix LiveView with a non-Phoenix UI | LiveView stays — it is the correct tool for the admin dashboard |
| CloudI idempotency handling | Idempotency is handled in MW-Core's `mw_router` (see REQ-002) |
| CloudI multi-tenancy implementation | Tenancy is enforced by MW-Core's core plane and reflected in CloudI service names |
| Vendor service code review | Integration Engineering owns vendor services — Platform Engineering reviews only routing configuration |
| CloudI for Elixir-owned adapters | The decision rule is clear: Elixir stays in MW-Core |
| CloudI cluster federation across data centres | Single cluster per deployment for now |

---

## 19. Open Questions

| # | Question | Owner | Required By |
|---|---|---|---|
| OQ-01 | Which vendor is the first Phase 2 target? Determines language template priority (Java vs Python vs other) | Product / Vendor Management | Before Phase 2 start |
| OQ-02 | Should CloudI external services share the same Kubernetes namespace as MW-Core pods, or run in a dedicated namespace? | Operations | Before Phase 1 deployment |
| OQ-03 | What is the agreed MaxR/MaxT policy for production external services? The `cloudi.conf` template uses 5 restarts in 60 seconds — is this acceptable to operations? | Operations / Platform Engineering | Before Phase 1 |
| OQ-04 | Should external service Docker images be built in the same CI pipeline as the umbrella, or separately? | DevOps | Before Phase 1 |
| OQ-05 | Is 10MB the right CloudI message size cap for file ingestion payloads? Large XML files from banking partners may exceed this | Integration Engineering | Before Phase 1 |
| OQ-06 | Should CloudI services be visible to the ops team independently via their own health endpoint, or only through the MW-Core admin dashboard? | Operations | Before Phase 0 complete |

---

## 20. Appendix

### A. Related Documents

| Document | Location |
|---|---|
| MW-Core System Design | `docs/system_design.md` |
| MW-Core Request Flow | `docs/request_flow.md` |
| MW-Core Complete System Document | `docs/Digital_Transformation_aligned_MW_Core.md` |
| Multi-Tenancy & Idempotency Requirements | `docs/MW_Core_Requirements_MultiTenancy_Idempotency.md` |
| CloudI Official Documentation | https://cloudi.org |
| CloudI API Reference | https://cloudi.org/api.html |
| CloudI Source Repository | https://github.com/CloudI/CloudI |

### B. Dependency Addition

```elixir
# apps/cloudi_bootstrap/mix.exs
defp deps do
  [
    {:cloudi_core, "~> 2.0"},
    {:mw_kernel, in_umbrella: true},
    {:infra_telemetry, in_umbrella: true}
  ]
end

# apps/adapter_cloudi/mix.exs
defp deps do
  [
    {:cloudi_core, "~> 2.0"},
    {:mw_kernel, in_umbrella: true},
    {:fuse, "~> 2.4"},
    {:jason, "~> 1.4"},
    {:infra_telemetry, in_umbrella: true}
  ]
end
```

### C. Integration Decision Rule — Quick Reference Card

> Print this and put it on the team wall.

```
┌─────────────────────────────────────────────────────┐
│         MW-CORE + CLOUDI DECISION RULE              │
│                                                     │
│  Did our team write it (or will we write it)        │
│  in Elixir?                                         │
│                                                     │
│     YES → Elixir adapter app in MW-Core             │
│      NO → CloudI external service in their language │
│                                                     │
│  We own the platform.                               │
│  CloudI owns the vendors.                           │
└─────────────────────────────────────────────────────┘
```

### D. File and Directory Structure at Phase 2 Complete

```
mw_core/
  apps/
    mw_kernel/              ← unchanged
    mw_auth/                ← unchanged
    mw_router/              ← unchanged (one new route type in ETS)
    mw_transform/           ← unchanged
    mw_audit/               ← unchanged
    gateway_api/            ← unchanged
    gateway_ws/             ← unchanged
    gateway_web/            ← CloudI panel added to dashboard
    gateway_mobile/         ← unchanged
    adapter_banking/        ← unchanged
    adapter_dw/             ← unchanged
    adapter_http/           ← unchanged
    adapter_file/           ← complex parsing delegated to CloudI
    infra_repo/             ← unchanged
    infra_cache/            ← unchanged
    infra_queue/            ← unchanged
    infra_telemetry/        ← CloudI metrics added
    cloudi_bootstrap/       ← NEW: CloudI runtime
    adapter_cloudi/         ← NEW: CloudI bridge adapter
  services/
    _templates/
      python_service/       ← Python template (copy for new services)
      java_service/         ← Java template (copy for new services)
    file_parser_py/         ← Phase 1: Python file parser
    [vendor_name]_java/     ← Phase 2: First vendor SDK
  config/
    cloudi.conf             ← CloudI service registry
  mix.exs                   ← umbrella root (two new apps added)
```

---

*MW-Core Platform Engineering — MomentPay*  
*Contact: prem@momentpay.in*  
*GitHub: https://github.com/momentpay/mw-core*  
*CloudI: https://cloudi.org*
