---
name: http_replay_sequence
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def http_replay_sequence(calls: list[dict], *, params: dict | None = None, extract: list[dict] | None = None, timeout_s: float = 30.0, verify_tls: bool = True, allow_redirects: bool = True, base_headers: dict | None = None) -> dict"
description: "Motor de replay HTTP: ejecuta en orden una secuencia de call specs (las que produce har_extract_calls_py_cybersecurity) compartiendo una sesion (cookie jar) entre pasos, con substitucion de parametros {{param}} y extraccion de valores de una respuesta para usarlos en pasos siguientes (p.ej. token CSRF del GET inicial -> header del POST). Pieza reutilizable del Nivel 1 (HTTP puro) del patron grabar->destilar->reproducir."
tags: [flow-replay, http, replay, client, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [re, requests]
tested: true
tests: ["test_golden_extract_and_subst", "test_edge_missing_param", "test_error_path_request_exception"]
test_file_path: "python/functions/infra/http_replay_sequence_test.py"
file_path: "python/functions/infra/http_replay_sequence.py"
params:
  - name: calls
    desc: "Lista ordenada de call specs. Cada spec: {method, url, headers(dict), cookies(dict opc), body(str|None), body_type:'json'|'form'|'raw'|None}. El body ya es texto (no se re-serializa). Es el formato de salida de har_extract_calls_py_cybersecurity."
  - name: params
    desc: "Dict inicial de contexto para la substitucion {{param}}. Se copia (no se muta el original). Pasa aqui secretos/tokens desde un vault/pass, nunca hardcodeados en los call specs."
  - name: extract
    desc: "Lista de reglas de extraccion {from: int|'last', type: 'json'|'regex'|'header'|'set_cookie', expr: str, as: str}. Se aplican justo tras ejecutar el step indicado en 'from' ('last' = el step recien ejecutado) y guardan el valor en ctx[as] para los pasos siguientes."
  - name: timeout_s
    desc: "Timeout por request en segundos (default: 30.0)."
  - name: verify_tls
    desc: "Verificar certificados TLS; se setea en la sesion (default: True). No desactivar salvo entorno de pruebas controlado."
  - name: allow_redirects
    desc: "Si los requests siguen redirects (default: True)."
  - name: base_headers
    desc: "Headers por defecto que se mezclan en la sesion (se aplican a todos los pasos). Util para User-Agent / Accept comunes."
output: "Dict {status: 'ok'|'error', steps: [{idx, method, url, status_code, ok, extracted, missing_params, error}], params_final: dict (ctx tras todos los pasos), error: str}. status='error' solo ante excepcion de transporte (requests.RequestException) o entrada invalida; en ese caso corta y deja de ejecutar. Un 4xx/5xx NO corta: el step queda con ok=False y status global sigue 'ok'."
---

## Ejemplo

```python
from infra import http_replay_sequence

# Requiere red: usa httpbin.org (publico). 2 pasos:
#   1) GET /uuid          -> extrae el uuid del JSON como param "u"
#   2) POST /anything     -> manda header X-Token: {{u}} (el uuid del paso 1)
calls = [
    {"method": "GET", "url": "https://httpbin.org/uuid",
     "headers": {"Accept": "application/json"}, "body": None, "body_type": None},
    {"method": "POST", "url": "https://httpbin.org/anything",
     "headers": {"X-Token": "{{u}}", "Content-Type": "application/json"},
     "body": '{"hello": "world"}', "body_type": "json"},
]
extract = [
    {"from": 0, "type": "json", "expr": "uuid", "as": "u"},
]

result = http_replay_sequence(calls, extract=extract)

print(result["status"])                       # "ok"
token = result["params_final"]["u"]           # el uuid extraido del paso 0
print("token:", token)
# httpbin /anything devuelve los headers que recibio; comprobamos que el
# paso 2 llevo el valor substituido:
print(result["steps"][1]["status_code"])      # 200
print(result["steps"][1]["ok"])               # True
# El header X-Token: {{u}} se substituyo por el uuid antes de enviarse.
```

## Cuando usarla

Usala tras `har_extract_calls_py_cybersecurity`, para validar que un flujo capturado se reproduce SIN navegador (Nivel 1 del patron grabar->destilar->reproducir). Es la base de las funciones-accion guardadas en el registry: cuando una secuencia HTTP demuestra reproducir un login + accion, se promueve a una funcion/pipeline dedicada. Tambien sirve para encadenar requests dependientes (token CSRF, session id, paginacion con cursor) compartiendo cookie jar y propagando valores entre pasos.

## Gotchas

- **Seguridad — secretos via params, nunca hardcodeados.** Los call specs pueden contener cookies/tokens. El caller debe inyectarlos via `{{param}}` desde un vault/pass (`params={...}`), no escribirlos en los specs ni commitearlos.
- **Seguridad — replay con efectos es PELIGROSO.** Reproducir una secuencia con efectos (POST que reinicia un server, borra, paga, envia) ejecuta esos efectos de verdad. El caller debe confirmar antes de lanzar una secuencia mutante.
- **Seguridad — `verify_tls` default True.** No lo pongas en False salvo en un entorno de pruebas controlado; desactivar la verificacion TLS abre la puerta a MITM.
- **Extraccion JSON es dot-path simple, NO jsonpath completo.** `"data.items.0.token"` funciona (claves + indices de lista por digito), pero no hay filtros, wildcards ni expresiones. Para casos complejos, usa `type: regex` o post-procesa.
- **Sigue redirects por defecto** (`allow_redirects=True`). Si la secuencia capturada depende del 302 explicito (p.ej. para leer el Location o una cookie intermedia), pon `allow_redirects=False`.
- **Params faltantes NO abortan.** Si un `{{nombre}}` no esta en ctx, se deja el literal `{{nombre}}` y se anade a `step.missing_params`. El request se envia igual; solo una excepcion de transporte corta la ejecucion.
- **El body no se re-serializa.** `body_type: "json"` solo documenta el tipo; el body ya es texto y se manda como `data=body`. Asegurate de incluir el header `Content-Type` adecuado en el spec.
- **4xx/5xx no es error global.** El step queda `ok=False` con su `status_code`, pero `status` global sigue `"ok"`. Solo `requests.RequestException` (DNS, conexion, timeout) marca `status="error"` y corta.

## Capability growth log

v1.0.0 — version inicial.
