# Phase 0 — Foundation

**Duration:** Weeks 1–2
**Status:** ✅ COMPLETE — 2026-04-26
**Goal:** Working Phoenix Umbrella skeleton with shared kernel, infra wiring, and CI.

---

## Deliverable

A deployable Phoenix Umbrella that:
- ✅ Boots cleanly in dev and prod
- ✅ Connects to MySQL via `infra_repo`
- ✅ Telemetry running (VM poller + ConsoleReporter wired)
- ✅ Passing unit tests: 16 tests, 0 failures
- ✅ `mix compile --warnings-as-errors` → 0 warnings

No business logic. No external system calls.

---

## What Was Built

### Umbrella transformation

The existing single-app `da_product_app` was converted to a Phoenix Umbrella:

| Change | Detail |
|--------|--------|
| `mix.exs` | Converted to umbrella form (`apps_path: "apps"`, no `:mod`) |
| `config/config.exs` | All `da_product_app` refs replaced with `infra_repo` + shared settings |
| `config/dev.exs` | DB now points to `mw_core_dev` via `infra_repo` |
| `config/test.exs` | DB now points to `mw_core_test` via `infra_repo` |
| `config/runtime.exs` | Simplified to prod-only `infra_repo` env var wiring |
| `lib/da_product_app*` | Archived to `_archive/lib/` — not deleted (history preserved) |

### Apps created

| App | Location | What's in it |
|-----|----------|-------------|
| `mw_kernel` | `apps/mw_kernel` | `Message`, `Context`, `Error` + `Adapter` & `Gateway` behaviours |
| `infra_repo` | `apps/infra_repo` | `InfraRepo.Repo` (Ecto/MyXQL), `Application`, initial migration |
| `infra_cache` | `apps/infra_cache` | `EtsCache` GenServer — lock-free ETS reads, write-serialised |
| `infra_queue` | `apps/infra_queue` | Supervisor skeleton (Broadway pipelines added in Phase 2) |
| `infra_telemetry` | `apps/infra_telemetry` | `Metrics` definitions + VM poller + ConsoleReporter |

### mw_kernel module inventory

```
apps/mw_kernel/lib/mw_kernel/
├── message.ex           # @enforce_keys, new/4, generate_id/0
├── context.ex           # new/0-1, halt/2, assign/3
├── error.ex             # 8 named constructors (unauthorized, upstream_error, …)
└── behaviours/
    ├── adapter.ex       # connect / send / health_check / disconnect
    └── gateway.ex       # ingest / respond
```

### infra_repo migrations

```
priv/repo/migrations/
└── 20260426000001_create_schema_info.exs   # version tracking table
```

### Dependency graph

```
mw_kernel   (no deps)
    ▲
    ├── infra_repo      (+ ecto_sql, myxql)
    ├── infra_cache
    ├── infra_queue     (+ infra_repo)
    └── infra_telemetry (+ telemetry_metrics, telemetry_poller, phoenix_live_dashboard)
```

No cycles. `mw_kernel` has zero umbrella dependencies.

---

## Tasks

### 1. Scaffold Umbrella ✅

```bash
# Converted from da_product_app single app:
# - mix.exs → apps_path: "apps", removed :mod
# - Archived lib/da_product_app* to _archive/lib/
# - Created apps/ directory with 5 umbrella apps
```

### 2. mw_kernel — Canonical Types ✅

```elixir
# apps/mw_kernel/lib/mw_kernel/message.ex
defmodule MwKernel.Message do
  @enforce_keys [:id, :type, :payload, :source]
  defstruct [:id, :type, :payload, :source, :metadata, :inserted_at]

  @spec new(atom(), map(), atom(), map()) :: t()
  def new(type, payload, source, metadata \\ %{}) ...
end

# apps/mw_kernel/lib/mw_kernel/context.ex
defmodule MwKernel.Context do
  defstruct [:trace_id, :tenant_id, :user, :roles,
             :request, :response, :adapter,
             halted: false, assigns: %{}, errors: []]

  @spec new(String.t()) :: t()
  @spec halt(t(), MwKernel.Error.t()) :: t()
  @spec assign(t(), atom(), term()) :: t()
end

# apps/mw_kernel/lib/mw_kernel/error.ex
defmodule MwKernel.Error do
  # 8 error codes: unauthorized | forbidden | not_found | bad_request
  #                upstream_error | rate_limited | circuit_open | internal_error
  def unauthorized/1, def forbidden/1, def not_found/1, def bad_request/1,
  def upstream_error/1, def rate_limited/1, def circuit_open/1, def internal_error/1
end
```

### 3. infra_repo — Ecto Setup ✅

```elixir
# config/config.exs
config :infra_repo,
  ecto_repos: [InfraRepo.Repo]

config :infra_repo, InfraRepo.Repo,
  migration_primary_key: [name: :id, type: :bigint, autogenerate: true]
```

Migration created: `20260426000001_create_schema_info.exs`

### 4. infra_cache — ETS Setup ✅

Implemented `InfraCache.EtsCache` with:
- Lock-free reads via `:ets.lookup/2` (no GenServer hop)
- `get/2`, `put/3`, `delete/2`, `all/1`
- Named table `:mw_cache` started by `InfraCache.Application`

### 5. infra_telemetry — LiveDashboard + Metrics ✅

Metrics defined: Phoenix, Ecto, `mw_router`, `mw_auth`, `infra_queue.dlq.depth`,
circuit breaker events, VM memory + run queue lengths.

`:telemetry_poller` wired with `:memory` and `:total_run_queue_lengths` built-ins.

### 6. CI Pipeline

CI quality jobs to be wired in Phase 1 (format + credo + dialyzer + coverage).
Current manual gates verified locally.

---

## Acceptance Criteria

- [x] `mix compile` produces zero warnings
- [x] `mix compile --warnings-as-errors` passes
- [x] `mix test` passes — 16 unit tests, 0 failures
- [x] All 5 umbrella apps generated and compiling
- [x] `mw_kernel` has zero umbrella deps (confirmed by graph)
- [x] MySQL Ecto config migrated to `infra_repo`
- [x] ETS cache starts and passes all CRUD tests
- [x] Telemetry supervisor starts, metrics list is non-empty
- [ ] `iex -S mix` full boot verified in dev (pending DB setup in target env)
- [ ] `GET /health/live` — added in Phase 1 (`gateway_api`)
- [ ] LiveDashboard route — added in Phase 4 (`gateway_web`)


  def get(table, key) do
    case :ets.lookup(table, key) do
      [{^key, value}] -> value
      [] -> nil
    end
  end
  def put(table, key, value), do: :ets.insert(table, {key, value})
  def delete(table, key), do: :ets.delete(table, key)
end
```

### 5. infra_telemetry — LiveDashboard + Metrics

Wire `phoenix_live_dashboard` into `gateway_web` (Phase 4 full build).
For now, start the telemetry supervisor and define metric schemas:

```elixir
defmodule InfraTelemetry.Metrics do
  import Telemetry.Metrics

  def metrics do
    [
      # Phoenix
      summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond}),
      counter("phoenix.router_dispatch.stop.duration", tags: [:route]),
      # Ecto
      summary("mw_core.repo.query.total_time", unit: {:native, :millisecond}),
      # Custom
      counter("mw_router.request.count", tags: [:adapter, :status]),
      summary("mw_router.request.duration", unit: {:native, :millisecond}, tags: [:adapter]),
      counter("mw_auth.failure.count", tags: [:reason]),
      last_value("infra_queue.dlq.depth")
    ]
  end
end
```

### 6. CI Pipeline

Set up GitHub Actions (or equivalent):

```yaml
jobs:
  quality:
    steps:
      - mix deps.get
      - mix compile --warnings-as-errors
      - mix format --check-formatted
      - mix credo --strict
      - mix dialyzer

  test:
    services:
      mysql: ...
    steps:
      - mix ecto.create
      - mix ecto.migrate
      - mix test --cover
```

---

## Acceptance Criteria

- [ ] `mix compile` produces zero warnings
- [ ] `mix format --check-formatted` passes
- [ ] `mix test` passes with at least smoke tests for app boot and DB connectivity
- [ ] `iex -S mix` starts all apps without errors
- [ ] MySQL connection pool starts and queries succeed
- [ ] Umbrella dependency graph has no cycles
