# Phase 10 — Adapter Lifecycle Management

## Overview

Phase 10 closes the **adapter creation gap**: before this phase, adding a new
integration required a developer to write Elixir code, compile it, and deploy
before it appeared anywhere in the UI. Phase 10 delivers two complementary
capabilities:

**Phase 10A — Config-Driven HTTP Adapter**
A fully runtime-configurable HTTP adapter that an operator can define through
the admin UI and have it live in Route Config and Flow Builder **immediately**,
with no code change, no restart, and no deployment. Covers REST APIs, payment
processors, fraud services, webhooks, and any JSON/HTTP endpoint.

**Phase 10B — Developer Scaffold Generator** *(dev mode only)*
A `mix mw.gen.adapter` task that produces a correctly-structured, ready-to-extend
umbrella app stub for custom-protocol adapters (ISO 8583, SFTP, Kafka, binary
protocols) where a generic HTTP adapter is insufficient. Phase 10B is
**intentionally absent from production/release builds** — it exists to start a
developer's workflow, not to run in a deployed system.

```
Phase 10A   Operator creates HTTP adapter in UI → live in seconds (all environments)
Phase 10B   Developer runs mix task → stub app generated → implements → ships (dev only)
```

Together these two tracks create a **complete adapter lifecycle**:

```
                    ┌─────────────────────────────────────────────────────┐
                    │               ADAPTER LIFECYCLE                      │
                    │                                                       │
  Any environment:  │  Admin UI → AdapterConfig DB → AdapterHttp.Configured│
                    │  (10A)   available immediately in Route Config + Flow │
                    │                                                       │
  Dev environment:  │  mix mw.gen.adapter → stub app generated             │
                    │  (10B)   developer implements → tests → PR → deploy   │
                    │          → appears in AdapterRegistry automatically   │
                    └─────────────────────────────────────────────────────┘
```

---

## What Changes From Phase 9

| Feature | Phase 9 | Phase 10 |
|---------|---------|---------|
| Adding a new adapter | Write Elixir code + compile + deploy | 10A: UI form → live in seconds |
| Adapter discovery | Scans compiled OTP apps only | Scans compiled apps + DB configs |
| Flow Builder adapter dropdown | Compiled adapters only | Compiled + DB-configured adapters |
| Developer scaffolding | Manual boilerplate | `mix mw.gen.adapter` generates stub |
| Dev-mode adapter creation UI | None | "Create Stub" button (dev mode only) |

---

## Implementation Status

| Sub-phase | Status | Notes |
|-----------|--------|-------|
| **10.0** Foundation: ConfigStore + Migration | ✅ **COMPLETE** | All files implemented and compiling |
| **10.1** Admin UI: Adapter Config CRUD | ✅ **COMPLETE** | `/admin/adapter-configs` with 4-tab modal |
| **10.2** Field Mapping UI + Test Panel | 🔲 Pending | Test tab placeholder exists; editors pending |
| **10.3** Flow Builder + Route Config Integration | ✅ **COMPLETE** | `AdapterRegistry` extended; DB adapters appear in dropdowns |
| **10.4** Phase 10B: Mix Generator | ✅ **COMPLETE** | `mix mw.gen.adapter NAME` + dev-mode UI card |
| **10.5** Hardening + Auth Encryption | 🔲 Pending | Cloak.Ecto, audit log, credo |

---

## Phase 10A — Config-Driven HTTP Adapter

### Design Principle

The majority of external systems speak HTTP/JSON. Rather than writing a new Elixir
module for each one, a single compiled `AdapterHttp.ConfiguredAdapter` module reads
its behaviour from a DB-backed config record. The router pipeline is completely
unaware of this — it calls `connect/1` and `send/2` just as with any compiled adapter.

```
RouteTable lookup
    │
    └── %{adapter: "AdapterHttp.ConfiguredAdapter", config: %{"name" => "fraud_guard"}}
                │
                ▼
   AdapterHttp.ConfiguredAdapter.connect(%{"name" => "fraud_guard"})
                │
                ▼
   Loads config from ETS cache (backed by adapter_configs DB table)
                │
                ▼
   Applies request_mapping (canonical → external field names)
                │
                ▼
   Makes HTTP call to base_url with auth headers injected
                │
                ▼
   Applies response_mapping (external → canonical field names)
                │
                ▼
   Returns {:ok, %MwKernel.Message{body: mapped_response}}
```

### Adapter Config Fields (as implemented)

| Field | Type | Description |
|---|---|---|
| `name` | string (unique) | Internal key used in flow canvas JSON, e.g. `"fraud_guard"` |
| `display_name` | string | Human label shown in UI dropdowns, e.g. `"Fraud Guard (Prod)"` |
| `description` | text | Optional description |
| `base_url` | string | Target URL, e.g. `https://fraud.internal/v1/score` |
| `http_method` | enum | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. Default: `POST` |
| `timeout_ms` | integer | Per-request timeout. Default: 5000. Max: 120000 |
| `retries` | integer | Retry count on failure (exponential backoff). Default: 0 |
| `auth_type` | enum | `none`, `bearer`, `basic`, `api_key` |
| `auth_key` | string | Header name (api_key) or username (basic) |
| `auth_value` | text | Token / password. **Phase 10.5**: encrypted via Cloak.Ecto |
| `extra_headers` | text (JSON) | Static extra headers: `{"X-Partner-Id": "abc"}` |
| `request_mapping` | text (JSON) | Field mapping canonical→external: `{"payment.amount": "txn.amt"}` |
| `response_mapping` | text (JSON) | Field mapping external→canonical: `{"result.status": "payment.status"}` |
| `enabled` | boolean | If false, excluded from AdapterRegistry discovery |
| `tenant_id` | string | Null = global; set to scope to a tenant |

### Field Mapping DSL (as implemented)

`AdapterHttp.FieldMapper` translates key names between MW-Core canonical payload format
and the external API's format using dot-path notation for nested fields.

```json
// request_mapping: canonical → external
{
  "payment.amount":   "data.txn.amt",
  "payment.currency": "data.txn.ccy",
  "customer.id":      "cust_ref"
}

// response_mapping: external → canonical
{
  "result.outcome": "payment.status",
  "txn_id":         "transaction.id"
}
```

Key behaviours:
- **Unknown fields** pass through unmodified (no data loss)
- `apply_request/2` translates outbound payload (canonical → external)
- `apply_response/2` translates inbound response using the inverse direction
- Passing `nil` or `%{}` as the mapping is a no-op (pass-through mode)

### DB Schema — `adapter_configs` Table (actual migration)

```sql
CREATE TABLE adapter_configs (
  id               BIGINT AUTO_INCREMENT PRIMARY KEY,
  name             VARCHAR(100) NOT NULL,
  display_name     VARCHAR(200) NOT NULL,
  description      TEXT,
  base_url         VARCHAR(500) NOT NULL,
  http_method      VARCHAR(10)  NOT NULL DEFAULT 'POST',
  timeout_ms       INT          NOT NULL DEFAULT 5000,
  retries          INT          NOT NULL DEFAULT 0,
  auth_type        VARCHAR(20)  NOT NULL DEFAULT 'none',
  auth_key         VARCHAR(200),
  auth_value       TEXT,                    -- Phase 10.5: Cloak.Ecto encrypted
  extra_headers    TEXT,                    -- JSON string
  request_mapping  TEXT,                    -- JSON string
  response_mapping TEXT,                    -- JSON string
  enabled          BOOLEAN      NOT NULL DEFAULT TRUE,
  tenant_id        VARCHAR(36),
  inserted_at      DATETIME     NOT NULL,
  updated_at       DATETIME     NOT NULL,
  UNIQUE KEY adapter_configs_name_index (name)
);
```

### Architecture (as built)

```
┌────────────────────────────────────────────────────────┐
│                    AdapterHttp app                      │
│                                                         │
│  AdapterHttp.ConfiguredAdapter   ← @behaviour MwKernel.Adapter
│       │                                                 │
│       ├── connect/1  → ConfigStore.get(name)           │
│       │               → {:ok, %{name, config}}         │
│       ├── send/2     → FieldMapper.apply_request        │
│       │               → Finch HTTP call                 │
│       │               → FieldMapper.apply_response      │
│       ├── health_check/1 → GET base_url (accepts 2xx-4xx as "up")
│       └── disconnect/1  → :ok                          │
│                                                         │
│  AdapterHttp.ConfigStore         ← GenServer + ETS      │
│       │                                                  │
│       ├── list/0      → all enabled configs             │
│       ├── get/1       → lookup by name                  │
│       └── reload!/0   → flush + reload from DB         │
│         (called automatically after every UI save/delete)
│                                                         │
│  AdapterHttp.FieldMapper                                │
│       ├── apply_request/2  → canonical → external       │
│       └── apply_response/2 → external → canonical       │
└────────────────────────────────────────────────────────┘
```

### Key Implementation Notes

**`connect/1` signature** — accepts either string-key or atom-key maps:
```elixir
# From route table / flow canvas JSON (string keys):
AdapterHttp.ConfiguredAdapter.connect(%{"name" => "fraud_guard"})

# From direct use (atom keys also accepted):
AdapterHttp.ConfiguredAdapter.connect(%{name: "fraud_guard"})
```

**`ConfigStore.reload!/0`** is called by the admin LiveView after every create,
update, or delete operation. No TTL; cache is authoritative until explicitly
invalidated.

**Auth header injection** (in order applied):
- `bearer`  → `Authorization: Bearer <auth_value>`
- `basic`   → `Authorization: Basic base64(<auth_key>:<auth_value>)`
- `api_key` → `<auth_key>: <auth_value>` (custom header name)
- `none`    → no auth header added

**GET requests**: payload keys are serialised as dot-path query string params.
All other methods send the payload as JSON body.

### Admin UI — `/admin/adapter-configs`

> **Note:** Route is `/admin/adapter-configs` (not `/admin/adapters`, which is
> already used by the circuit-breaker health view `AdapterHealthLive`).

LiveView `GatewayWebWeb.AdapterConfigsLive` provides:

**Adapters Table**

| Display Name | URL | Auth | Status | Health | Actions |
|---|---|---|---|---|---|
| Fraud Guard (Prod) | fraud.internal/v1/score | bearer | enabled | ● healthy | Edit / Delete |
| Payment Gateway Pro | pay.example.com | api_key | enabled | ● healthy | Edit / Delete |
| Legacy CRM | crm.internal | basic | disabled | — | Edit / Delete |

**Create / Edit Modal — 4 Tabs**

- **Basic** — Name (snake_case key), Display Name, Description, Base URL, HTTP Method, Timeout, Retries
- **Auth** — Auth type dropdown; conditional fields for key name / credential value
- **Field Mapping** — `request_mapping` and `response_mapping` JSON text areas with dot-path notation hint
- **Test** *(placeholder, Phase 10.2)* — test panel coming in next sub-phase

**Other controls:**
- Enable/Disable toggle — updates `enabled` column and calls `ConfigStore.reload!/0`
- Delete with confirmation modal
- "Check Health" button — async probes all configured adapters via `AdapterRegistry.list_with_health/0`
- Flash banner for save/delete confirmations

**Phase 10B dev-mode card** — shown only when `Application.get_env(:gateway_web, :env) == :dev`:

```
┌─────────────────────────────────────────────────────────┐
│  DEV   Developer Tools                                   │
│                                                          │
│  Generate a stub adapter OTP application scaffold.       │
│  Available in development only. Requires                 │
│  mix compile and restart after generation.               │
│                                          [Create Stub]   │
└─────────────────────────────────────────────────────────┘
```

### AdapterRegistry Integration (Phase 10.3)

`MwRouter.AdapterRegistry` was extended with `discover_configured/0` which:

1. Checks if `AdapterHttp.ConfigStore` is loaded (graceful fallback if not)
2. Calls `ConfigStore.list/0` to get all enabled DB configs
3. Returns one virtual adapter entry per config with:
   - `module: AdapterHttp.ConfiguredAdapter`
   - `display_name: cfg.display_name` (unique per config row)
   - `connect_config: %{"name" => cfg.name}` (passed to `connect/1` during health probe)

The merged list (compiled + DB-configured) is deduplicated by `display_name` and
sorted alphabetically. Both `list_with_health/0` and `list/0` include DB adapters.

Health probes for configured adapters pass `connect_config` to `connect/1` so each
entry is probed against its correct endpoint.

---

## Phase 10B — Developer Scaffold Generator

### Design Principle

Phase 10B exists **only for developers** to bootstrap a new custom-protocol adapter.
It is a `Mix` task — Mix does not exist in compiled releases. The task is guarded at
both the mix task level (compile-time env check) and the admin UI (runtime env check)
so it can never appear in a production deployment.

```
         DEV environment                  PRODUCTION environment
         ┌──────────────────────┐         ┌──────────────────────┐
         │ mix mw.gen.adapter   │ ✓ works  │ mix mw.gen.adapter   │ ✗ raises error
         │ "Create Stub" in UI  │ ✓ shown  │ "Create Stub" in UI  │ ✗ hidden (env check)
         └──────────────────────┘         └──────────────────────┘
```

### Mix Task: `mix mw.gen.adapter` (as implemented)

```bash
# Usage
mix mw.gen.adapter NAME [--module MODULE]

# NAME must be lowercase snake_case
# Examples
mix mw.gen.adapter my_crm
mix mw.gen.adapter fraud_guard --module AdapterFraudGuardV2
mix mw.gen.adapter iso_banking
```

**Arguments:**

| Argument/Flag | Description |
|---|---|
| `NAME` | snake_case identifier. App becomes `adapter_<name>`, module becomes `Adapter<CamelCase>` |
| `--module MODULE` | Override the generated top-level module name |

The task raises `Mix.raise` with a clear error message when `Mix.env() == :prod`.

### Generated File Tree (as implemented)

```
apps/adapter_<name>/
├── mix.exs                                  ← umbrella app with mw_kernel dep
├── lib/
│   ├── adapter_<name>.ex                    ← top-level module (stub + moduledoc)
│   └── adapter_<name>/
│       ├── application.ex                   ← OTP Application supervisor
│       └── client.ex                        ← MwKernel.Adapter @behaviour (all 4 callbacks)
└── test/
    └── adapter_<name>_test.exs              ← connect / health_check / disconnect tests
```

### Generated `client.ex` callbacks

All four `MwKernel.Adapter` callbacks are generated as stubs with `TODO` comments:

```elixir
@impl true
def connect(_config) do
  {:ok, %{}}   # TODO: open connection / load credentials
end

@impl true
def send(_state, %MwKernel.Message{} = message) do
  Logger.warning("[AdapterMyAdapter.Client] send/2 not yet implemented")
  {:ok, message}   # TODO: call downstream, return {:ok, response_msg} | {:error, reason}
end

@impl true
def health_check(_state), do: :ok  # TODO: probe downstream health

@impl true
def disconnect(_state), do: :ok
```

After generation the developer:
1. Implements the callbacks in `client.ex`
2. Runs `mix compile` (or has the watcher running)
3. Restarts the Phoenix server
4. The new adapter appears automatically in Flow Builder and Route Config dropdowns
   via `AdapterRegistry` discovery

### Dev/Production Environment Guard (as implemented)

```elixir
# config/config.exs — exposes Mix env to runtime
config :gateway_web, :env, config_env()

# In AdapterConfigsLive render — guards the dev-mode card
<%= if @dev_mode do %>
  <!-- "Create Stub Adapter" card shown here -->
<% end %>

# @dev_mode is set at mount:
dev_mode: Application.get_env(:gateway_web, :env) == :dev

# In mix task — raises at runtime in prod
def run(_args) do
  if Mix.env() == :prod do
    Mix.raise("mix mw.gen.adapter is not available in production environments. " <>
              "Create adapters via the config-driven AdapterHttp.ConfiguredAdapter instead.")
  end
  # ... generator logic
end
```

---

## Sub-Phases

### ✅ Phase 10.0 — Foundation: ConfigStore + Migration

**Completed 2026-05-01**

- `adapter_configs` migration (`20260501000001_create_adapter_configs.exs`)
- `InfraRepo.Schemas.AdapterConfig` Ecto schema with changeset validation
- `AdapterHttp.ConfigStore` GenServer + named ETS table `:adapter_http_configs`
  - Loads all `enabled = true` rows at startup (graceful on DB not ready)
  - `reload!/0` flushes and rebuilds from DB; called after every CRUD save/delete
- `AdapterHttp.FieldMapper` — dot-path `apply_request/2` and `apply_response/2`
- `AdapterHttp.ConfiguredAdapter` — full `@behaviour MwKernel.Adapter` implementation
  - `connect/1` accepts `%{"name" => key}` or `%{name: key}`
  - `send/2` applies field mappings, handles all HTTP methods via Finch
  - `health_check/1` — GET to base_url; 2xx–4xx counts as "reachable"
  - Exponential backoff retry
- `AdapterHttp.Application` updated to start `ConfigStore` in supervisor tree
- `adapter_http/mix.exs` — added `{:infra_repo, in_umbrella: true}` dependency

**Deliverable:** `AdapterHttp.ConfiguredAdapter.connect(%{"name" => "my_adapter"})` ✅

---

### ✅ Phase 10.1 — Admin UI: Adapter Config CRUD

**Completed 2026-05-01**

- `GatewayWebWeb.AdapterConfigsLive` at `/admin/adapter-configs`
- Table listing all configs: display name, URL, auth type, enabled status, health pill, actions
- Create/Edit modal with 4 tabs: Basic, Auth, Field Mapping, Test (placeholder)
- Enable/Disable toggle (updates `enabled` column, calls `ConfigStore.reload!/0`)
- Delete with confirmation modal
- Background health probe button (async `AdapterRegistry.list_with_health/0`)
- Flash banners for save/delete/toggle confirmations
- Phase 10B dev-mode "Create Stub Adapter" card (hidden in non-dev)
- Route registered: `live "/admin/adapter-configs", AdapterConfigsLive, :index`
- Dashboard quick link added: "Adapter Configs — Runtime HTTP adapter setup"

**Deliverable:** Operator creates an HTTP adapter in the UI; `ConfigStore.reload!/0` makes
it available in `AdapterRegistry` immediately. ✅

---

### 🔲 Phase 10.2 — Field Mapping UI + Test Panel

- Test tab: send sample payload, see raw request/response and mapped result
- Field mapping tab enhancements: live preview of mapping transforms
- "Test connection" — fires `health_check/1` and shows latency

**Deliverable:** Operator verifies `amount → data.txn.amt` transformation with a live call.

---

### ✅ Phase 10.3 — Flow Builder + Route Config Integration

**Completed 2026-05-01**

- `AdapterRegistry.discover_configured/0` added — reads all enabled configs from
  `AdapterHttp.ConfigStore` and creates one virtual adapter entry per row
- Virtual entries have `connect_config: %{"name" => cfg.name}` so health probes
  use the correct `connect/1` path
- `list_with_health/0` and `list/0` both include DB adapters merged with compiled adapters
- Deduplication by `display_name`; sorted alphabetically
- Flow Builder and Route Config dropdowns automatically show DB-configured adapters
  (no changes to those LiveViews required — they already call `AdapterRegistry`)

**Deliverable:** A configured adapter (e.g. "Fraud Guard") appears in Flow Builder
dropdown alongside compiled adapters (`AdapterBanking`, etc.). ✅

---

### ✅ Phase 10.4 — Phase 10B: Mix Generator

**Completed 2026-05-01**

- `Mix.Tasks.Mw.Gen.Adapter` — `mix mw.gen.adapter NAME [--module MODULE]`
- Generates: `mix.exs`, top-level stub module, OTP application, client with all 4
  callbacks (with `TODO` stubs), and test file
- Validates NAME is snake_case; refuses if app directory already exists
- Raises `Mix.raise` if `Mix.env() == :prod`
- Prints "Next steps" guide after generation
- Dev-mode UI card in `AdapterConfigsLive` with "Create Stub Adapter" button
  (visible only when `Application.get_env(:gateway_web, :env) == :dev`)
- `config :gateway_web, :env, config_env()` added to `config/config.exs`

**Deliverable:** `mix mw.gen.adapter my_crm` → compilable stub in `apps/adapter_my_crm/`. ✅

---

### 🔲 Phase 10.5 — Hardening + Auth Encryption

- `auth_value` column encrypted at rest via `Cloak.Ecto` (AES-256-GCM)
  — add `{:cloak_ecto, "~> 1.3"}` to root `mix.exs`
- Audit log entry on every adapter config create/update/delete
- `mix credo --strict` clean on all new modules
- Integration tests: `configured_adapter_test.exs`, `config_store_test.exs`,
  `field_mapper_test.exs` using `Bypass` stubs
- `adapter_registry_test.exs` — DB adapters merge into list

**Deliverable:** Auth credentials encrypted at rest; all config changes audited; tests ≥ 80%.

---

## File Map

### New Files (implemented)

```
apps/infra_repo/priv/repo/migrations/
  20260501000001_create_adapter_configs.exs    ✅

apps/infra_repo/lib/infra_repo/schemas/
  adapter_config.ex                            ✅  Ecto schema + decode_json/1

apps/adapter_http/lib/adapter_http/
  config_store.ex                              ✅  GenServer + ETS :adapter_http_configs
  field_mapper.ex                              ✅  apply_request/2, apply_response/2
  configured_adapter.ex                        ✅  @behaviour MwKernel.Adapter

apps/gateway_web/lib/gateway_web_web/live/
  adapter_configs_live.ex                      ✅  /admin/adapter-configs LiveView

apps/gateway_web/lib/mix/tasks/
  mw.gen.adapter.ex                            ✅  mix mw.gen.adapter task (dev only)
```

### Modified Files (implemented)

```
apps/adapter_http/lib/adapter_http/application.ex
  ✅ ConfigStore added to supervisor tree

apps/adapter_http/mix.exs
  ✅ {:infra_repo, in_umbrella: true} dependency added

apps/mw_router/lib/mw_router/adapter_registry.ex
  ✅ discover_configured/0 added; probe_health updated for connect_config

apps/gateway_web/lib/gateway_web_web/router.ex
  ✅ live "/admin/adapter-configs", AdapterConfigsLive, :index

apps/gateway_web/lib/gateway_web_web/live/dashboard_live.ex
  ✅ "Adapter Configs" quick link added

config/config.exs
  ✅ config :gateway_web, :env, config_env()
```

### Test Files (pending — Phase 10.5)

```
apps/adapter_http/test/
  configured_adapter_test.exs      ← connect/send/health with Bypass stubs
  config_store_test.exs            ← cache invalidation, DB fallback
  field_mapper_test.exs            ← apply_request/apply_response edge cases

apps/gateway_web/test/live/
  adapter_configs_live_test.exs    ← CRUD operations, enable/disable, delete

apps/mw_router/test/
  adapter_registry_test.exs        ← DB adapters merged into list
```

---

## Dependencies

| Package | Version | Reason | Status |
|---|---|---|---|
| `finch` | already present | HTTP client for ConfiguredAdapter | ✅ in use |
| `infra_repo` | in_umbrella | DB access from adapter_http | ✅ added |
| `cloak_ecto` | `~> 1.3` | Encrypt `auth_value` at rest | 🔲 Phase 10.5 |
| `jason` | already present | JSON field mapping decode | ✅ in use |

---

## Environment Behaviour Summary

| Capability | Dev | Staging | Production |
|---|---|---|---|
| Create HTTP adapter in UI (10A) | ✅ | ✅ | ✅ |
| Edit / disable adapter config | ✅ | ✅ | ✅ |
| Adapters appear in Flow Builder | ✅ | ✅ | ✅ |
| Adapters appear in Route Config | ✅ | ✅ | ✅ |
| `mix mw.gen.adapter` task (10B) | ✅ | ✗ (raises) | ✗ (raises) |
| "Create Stub" card in admin UI | ✅ | ✗ (hidden) | ✗ (hidden) |
| Test panel in adapter modal | 🔲 Phase 10.2 | 🔲 | 🔲 |
| `auth_value` encrypted at rest | 🔲 Phase 10.5 | 🔲 | 🔲 |

---

## Definition of Done

- [x] `AdapterHttp.ConfiguredAdapter` implements all 4 `MwKernel.Adapter` callbacks
- [x] Creating an adapter config in UI triggers `ConfigStore.reload!/0` — available in dropdowns immediately
- [x] DB-configured adapters appear in Flow Builder adapter dropdown
- [x] DB-configured adapters appear in Route Config adapter dropdown
- [x] `AdapterRegistry.list_with_health/0` includes both compiled and DB-configured adapters
- [x] `mix mw.gen.adapter NAME` produces a compilable stub with all 4 callbacks
- [x] Generator raises `Mix.raise` when `Mix.env() == :prod`
- [x] "Create Stub" UI card is hidden when `Application.get_env(:gateway_web, :env) != :dev`
- [x] `config :gateway_web, :env, config_env()` in config.exs
- [ ] Test panel (Phase 10.2): send sample payload, see raw response and mapped result
- [ ] `auth_value` is encrypted at rest (Phase 10.5); never appears in logs
- [ ] All adapter config changes are recorded in the audit log (Phase 10.5)
- [ ] `mix credo --strict` passes on all new modules (Phase 10.5)
- [ ] Test coverage ≥ 80% on `ConfiguredAdapter`, `ConfigStore`, `FieldMapper` (Phase 10.5)
- [ ] Integration test: create config → publish flow → send request → verify response (Phase 10.5)

---

## Open Questions

| # | Question | Owner | Resolution |
|---|---|---|---|
| 1 | Should `auth_value` use `Cloak.Ecto` or Vault transit secrets engine? | Security | Phase 10.0: stored plaintext with note. Phase 10.5: `Cloak.Ecto` (simpler, no Vault dependency). Migrate to Vault in a future hardening phase if required. |
| 2 | Should the Test panel make a real external call or use a sandbox mode? | Product | Real call by default; add a `dry_run` toggle in Phase 10.2 that validates config structure without hitting the URL. |
| 3 | Can a configured adapter be used in composite (Phase 7) fan-out as well as DAG (Phase 9) flows? | Engineering | Yes — `ConfiguredAdapter` is a standard `MwKernel.Adapter` and is transparent to `FanoutDispatcher`. |
| 4 | What happens if a configured adapter's base_url changes while a flow is executing? | Engineering | `ConfigStore` reloads on explicit `reload!/0` call only. Running requests use the state from `connect/1`. Config change takes effect on next dispatch. |
| 5 | Should the generator task create the test DB and run tests automatically? | Engineering | No. Generator only writes files. Developer runs `mix test apps/adapter_<name>` manually. |
| 6 | Multiple field mapping operations (rename + transform + drop)? | Product | Phase 10.2: rename only. Transform functions (truncate, uppercase, parse_int) deferred to Phase 11 transform engine. |
| 7 | Should generated adapter stub include a `Transformer` module? | Engineering | Current impl: single `client.ex` with all 4 callbacks. Transformer pattern documented in `adapter_development_guide.md` for developers who want it; not generated by default to keep the stub minimal. |
