# Phase F — Request XPath / Search Key Management UI

**Branch**: `feat/fraud-xpath-search-keys`
**Parent**: `feat/fraud-rules-jube-parity`
**Jube reference**: `EntityAnalysisModelRequestXPath` POCO + Image-1 "Request XPath" tab.

---

## 1. Goal

Expose the existing `risk_request_xpaths` table through an admin LiveView so
operators can:

1. Declare which JSON paths in an incoming payload become first-class **fields**
   (used by the RuleBuilder field selector as data source).
2. Mark a field as a **Search Key** with TTL/cache settings — the prerequisite
   for AbstractionEngine `SearchValue` / `SearchInterval` queries.
3. Toggle `report_table` / `response_payload` to propagate the value into
   downstream sinks.

Currently the RuleBuilder reads fields from the live payload only; once F
ships it reads from **declared XPaths** with typed default values.

---

## 2. Source-of-truth field mapping

Schema already complete at [`risk_request_xpath.ex`](../../../apps/infra_repo/lib/infra_repo/schemas/risk_request_xpath.ex)
(20 fields). Jube has 4 extras we intentionally skip:

| Jube field | Decision |
|---|---|
| `Guid`, `ImportId`              | Skip — integer id sufficient |
| `Deleted`, `DeletedUser/Date`   | Skip — hard delete |
| `CreatedUser`, `UpdatedUser`    | Add `created_by` (string), drop the rest |
| `Version`                       | Add `version` integer (bump on update) |

---

## 3. Work breakdown

### 3.1 Migration

- `add :version, :integer, default: 1, null: false`
- `add :created_by, :string`
- Index on `(tenant_id, entity_model_id)` if absent.

### 3.2 Schema

Extend `RiskRequestXpath`:
- Add `version` + `created_by` to fields & `@all_fields`.
- Add `validate_number(:search_key_ttl_value, greater_than: 0)`.
- Add `validate_number(:search_key_fetch_limit, greater_than: 0, less_than_or_equal_to: 10_000)`.

### 3.3 Context (`InfraRepo.Risk.Risks`)

Append helpers (follow the existing `list_rules/get_rule/upsert_rule/delete_rule` pattern):
- `list_xpaths(entity_model_id, opts \\ [])`
- `get_xpath(id)`
- `upsert_xpath(attrs)` — bumps `version` on update
- `delete_xpath(id)`
- `payload_field_options(entity_model_id)` — returns `[{name, data_type, default}]` list for the RuleBuilder field selector dropdown.

### 3.4 LiveView

`apps/gateway_web/lib/gateway_web_web/live/admin/fraud/request_xpaths_live.ex`
mounted at `/admin/fraud/request-xpaths`. Mirror `ListsLive`:

- Model filter dropdown (top toolbar) → reuses `entity_models` picker.
- Table columns: Name · XPath · Data Type · Search Key · Cache · Default · Active · Updated · Actions.
- "+ New XPath" → slide-over drawer (`max-w-lg`).
- Drawer sections (collapsible):
  1. **Properties** — name, xpath, data type, default value, active, locked.
  2. **Search Key** — enable toggle reveals: ttl_interval/value, fetch_limit, cache toggle (reveals cache_interval/value/limit).
  3. **Output** — `report_table`, `response_payload`.
- Inline XPath validator: `Jason.encode!/1` on a synthetic `%{}`-path matcher (or a tiny `JsonPath.valid?/1` helper in `mw_kernel`).
- Optimistic save with `phx-debounce="500"`.

### 3.5 RuleBuilder integration

`MwRiskWeb.RuleBuilderComponent` (Phase D) currently builds the field dropdown from `Map.keys(payload_sample)`. Change to:

```elixir
options =
  case Risks.payload_field_options(entity_model_id) do
    [] -> Map.keys(payload_sample) |> Enum.sort()
    xs -> Enum.map(xs, fn {n, t, _} -> {n, "#{n} (#{t})"} end)
  end
```

This lets Gateway/Abstraction/Activation rule authors pick from declared XPaths.

### 3.6 Sidebar

Add row in admin_layout under "Fraud":
- `Request XPaths` → `/admin/fraud/request-xpaths`

### 3.7 Tests

- `apps/infra_repo/test/schemas/risk_request_xpath_test.exs` — required-field, interval-validation, version-bump.
- `apps/gateway_web/test/live/admin/fraud/request_xpaths_live_test.exs` — list, drawer open, save, validation error.

### 3.8 Seeds

Append 6 XPaths spanning data types:

| Name | XPath | Data Type | Search Key | Default |
|---|---|---|---|---|
| amount         | `$.amount`           | float    | no  | 0       |
| currency       | `$.currency`         | string   | no  | USD     |
| merchant_id    | `$.merchant.id`      | string   | yes (24h) | —       |
| card_pan       | `$.card.pan`         | string   | yes (90d) | —       |
| ip_address     | `$.context.ip`       | string   | yes (1h)  | 0.0.0.0 |
| customer_email | `$.customer.email`   | string   | yes (90d) | —       |

---

## 4. Acceptance criteria

- [ ] CRUD on `/admin/fraud/request-xpaths` works end-to-end.
- [ ] Saving an XPath bumps `version` by 1.
- [ ] RuleBuilder field selector lists declared XPaths instead of raw payload keys when any are defined.
- [ ] AbstractionEngine still resolves `SearchKey` against the declared XPath name.
- [ ] All 14 tests (8 schema + 6 LiveView) pass.

---

## 5. Out of scope (deferred)

- Live XPath tester ("paste a JSON, see resolved values").
- Bulk import from CSV / OpenAPI schema.
- XPath aliasing / computed XPaths (`$.amount * 100`).
- Per-XPath data masking (PCI redaction in audit logs).
