# Flow Replay — Guardar un flujo web como función reproducible

Tag: `flow-replay`. Grupo de funciones para convertir un flujo de navegador que se hizo
una vez a mano (login en un panel, reiniciar un servidor, rellenar un formulario) en una
**función del registry reproducible sin intervención**. Materializa la doctrina del issue
0087: el registry crece promoviendo secuencias repetidas a operaciones de un solo paso.

Filtro MCP: `mcp__registry__fn_search query="" tag="flow-replay"`.

Complementa al grupo [`web-proxy`](web-proxy.md): `web-proxy` **graba** el tráfico,
`flow-replay` lo **destila y reproduce**.

## El patrón: grabar → destilar → reproducir

Tres fases, con una jerarquía de reproducción de más barato a más caro:

```
Fase 0 — GRABAR (una vez, siempre con browser + proxy)
  web_proxy ON  →  haces la acción a mano en el navegador  →  exportas el tramo a HAR
  (funciones del grupo web-proxy: start_mitm_capture, launch_chromium_proxy, query_mitm_flows --har)

Fase 1 — DESTILAR (del HAR a una secuencia de requests)
  har_filter_flows   → descarta estáticos/analytics, deja los flujos que importan
  har_extract_calls  → normaliza cada flujo a una "call spec" reproducible (método, url,
                       headers, cookies, body), aislando los datos de auth

Fase 2 — REPRODUCIR, en orden de preferencia:
  Nivel 1  HTTP puro       http_replay_sequence — rápido, headless, scriptable. PREFERIDO.
  Nivel 2  headless chromium (fallback)  — cuando hay token dinámico firmado en cliente,
                            challenge JS o WAF con fingerprint de navegador. Reutiliza
                            cdp_extract_recipe + cdp_save_storage_state (ver Fronteras).
  Nivel 3  chromium visible + acciones humanizadas — último recurso si headless es detectado
                            (cdp_click_xy_human, cdp_move_mouse_human del dominio browser).
```

La función-acción concreta que guardas en el registry (`reboot_<panel>_server`,
`login_<panel>`, etc.) envuelve el nivel que funcione: idealmente una llamada a
`http_replay_sequence` con su secuencia + parámetros, y los secretos resueltos desde
`pass`/vault.

## Funciones del grupo

| ID | Firma corta | Qué hace |
|---|---|---|
| [har_filter_flows_py_cybersecurity](../../python/functions/cybersecurity/har_filter_flows.md) | `har_filter_flows(har, *, hosts, methods, drop_static, drop_analytics) -> list[dict]` | Filtra un HAR: descarta recursos estáticos y hosts de telemetría, deja los flujos candidatos a "acción". Pura. |
| [har_extract_calls_py_cybersecurity](../../python/functions/cybersecurity/har_extract_calls.md) | `har_extract_calls(entries, *, drop_headers) -> list[dict]` | Convierte entries HAR en "call specs" normalizadas (método/url/headers/cookies/body/body_type), aislando cookies de auth y descartando headers hop-by-hop. Pura. |
| [http_replay_sequence_py_infra](../../python/functions/infra/http_replay_sequence.md) | `http_replay_sequence(calls, *, params, extract, timeout_s, verify_tls, allow_redirects, base_headers) -> dict` | Motor de replay HTTP: ejecuta la secuencia compartiendo cookie jar, substituye `{{param}}` y extrae valores de una respuesta para inyectarlos en pasos siguientes (flujo CSRF-like). Impura. |

## Ejemplo canónico end-to-end

Destilar un HAR capturado y reproducir el flujo sin navegador. Las tres funciones se
encadenan; la extracción del paso 1 (un token) se inyecta en el paso 2:

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from cybersecurity.har_filter_flows import har_filter_flows
from cybersecurity.har_extract_calls import har_extract_calls
from infra.http_replay_sequence import http_replay_sequence

# 1. HAR exportado por:  query_mitm_flows ~/captures/traffic-*.mitm --har ~/sesion.har
import json
har = json.load(open(os.path.expanduser("~/sesion.har")))

# 2. Destilar: del ruido a la secuencia mínima
flows = har_filter_flows(har, hosts=["panel.midominio.com"])   # solo el host del panel
calls = har_extract_calls(flows)                                # call specs reproducibles

# 3. Reproducir (Nivel 1, HTTP puro). El token del GET inicial se inyecta en el POST.
res = http_replay_sequence(
    calls,
    params={"server_id": "vps-42"},                            # parametrizado por el caller
    extract=[{"from": 0, "type": "json", "expr": "csrf", "as": "csrf"}],
    verify_tls=True,
)
print(res["status"], [s["status_code"] for s in res["steps"]])
```

Una vez validado, el flujo se promueve a una función-acción nombrada del registry
(p. ej. `reboot_vps_server_<panel>`) que internamente llama a `http_replay_sequence`
con su secuencia fija, recibe los parámetros del caller y resuelve los secretos desde
`pass`. Esa función-acción es lo que el agente invoca en un solo paso a partir de entonces.

## Fronteras

- **No graba**: la captura es del grupo [`web-proxy`](web-proxy.md). Este grupo empieza
  con un HAR ya existente.
- **No auto-parametriza** (todavía). `har_extract_calls` normaliza pero NO detecta solo
  qué valor es un token dinámico ni dónde se reinyecta. La parametrización (`{{param}}`)
  y las reglas de `extract` las decide el humano/agente leyendo el HAR. La detección
  automática de tokens/CSRF sería una función nueva del grupo, no una ampliación.
- **No incluye el runner de Nivel 2/3** (browser fallback). Está especificado en el
  patrón pero no implementado: cuando un flujo real falle en HTTP puro, se construye un
  "action recipe" reutilizando casi entero `cdp_extract_recipe_py_pipelines` (mismo
  formato YAML, steps de acción en vez de extracción) + `cdp_save_storage_state_go_browser`
  para saltarse el login. No se construye por adelantado (KISS / registry-first).
- **No gestiona secretos**: los secretos viajan como `{{param}}` desde `pass`/vault. El
  grupo nunca los hardcodea ni los persiste.

## Gotchas (seguridad — leer antes de usar)

- **El HAR es sensible**: contiene cookies y tokens en crudo. Trátalo como un secreto —
  gitignored, no subir a Gitea, no indexar, borrar tras destilar. El output de
  `har_extract_calls` también lleva esos valores hasta que los sustituyes por `{{param}}`.
- **Secretos a `pass`/vault**, nunca en el código de la función-acción.
- **Replay con efectos = peligroso**: reproducir un POST que reinicia, borra o paga es
  destructivo. La función-acción debe pedir confirmación o exponer un flag explícito
  (`--yes`/`confirm=True`) antes de disparar. Nunca replay ciego de una acción irreversible.
- **HTTP puro no siempre reproduce**: token firmado en cliente, challenge JS, o WAF que
  exige fingerprint de navegador → cae a Nivel 2 (headless) o 3 (visible humanizado).
- `http_replay_sequence` sigue redirects por defecto y `verify_tls=True`. La extracción
  JSON es dot-path simple (`a.b.0.c`), no JSONPath completo.

## Prerequisitos

- Fase 0 (grabar): grupo `web-proxy` operativo (mitmproxy + chromium). Ver su página.
- Fase 1-2: `requests` en `python/.venv` (ya presente). Sin dependencias nuevas.
