--- 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.