Files
fn_registry/docs/execution_standard.md
egutierrez eb30074792 chore: auto-commit (8 archivos)
- .claude/rules/registry_calls.md
- apps/dag_engine/README.md
- apps/dag_engine/app.md
- docs/capabilities/INDEX.md
- docs/capabilities/systemd.md
- docs/execution_standard.md
- dev/proposals_e2e_checks_0121/
- docs/capabilities/backends.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:30 +02:00

13 KiB

Estandar de Ejecucion: YAML + Exit Codes + Hooks

Contrato comun para apps, pipelines y automatizaciones del fn-registry. Define como declarar flujos en YAML, que exit codes devolver, como reportar resultados y como integrarse con dag_engine y operations.db.


Motivacion

Tenemos dos patrones que ya funcionan:

  • dag_engine (apps/dag_engine/): orquesta DAGs con command/function, usa exit codes para decidir flujo, soporta handlers (init/success/failure/exit) y scheduling cron via schedule:.
  • script_navegador: ejecuta pasos tipados (CDP actions) desde YAML, registra todo en operations.db, sale con 0/1.

Ambos comparten la misma idea: YAML declara el flujo, exit code señala el resultado, logs estructurados permiten analisis posterior. Este documento formaliza ese contrato para que cualquier app nueva lo siga desde el inicio.


1. Estructura YAML

Toda app que ejecute un flujo debe leer un YAML con esta estructura minima:

# Cabecera obligatoria
name: nombre_del_flujo
description: "Que hace este flujo"  # opcional pero recomendado

# Variables de entorno disponibles para todos los pasos
env:                                 # opcional
  KEY: value

# Pasos del flujo
steps:
  - name: paso_1                     # identificador unico dentro del flujo
    action: tipo_de_accion           # dispatch key (navigate, command, query, etc.)
    # ... parametros especificos del action
    continue_on_error: false         # default: false
    timeout_ms: 30000                # default: 30s, 0 = sin timeout
    depends: []                      # IDs de pasos previos (vacio = secuencial)

# Hooks opcionales
hooks:
  on_success: "comando o script"     # se ejecuta si exit code = 0
  on_failure: "comando o script"     # se ejecuta si exit code = 1
  on_partial: "comando o script"     # se ejecuta si exit code = 2

Campos de la cabecera

Campo Requerido Descripcion
name si Identificador del flujo (snake_case)
description no Descripcion legible
env no Variables de entorno (mapa key-value)
steps si Lista de pasos a ejecutar
hooks no Comandos a ejecutar segun resultado

Campos de cada step

Campo Requerido Default Descripcion
name si ID unico del paso dentro del flujo
action si Tipo de accion (cada app define sus propios actions)
continue_on_error no false Si true, un fallo no detiene el flujo
timeout_ms no 30000 Timeout del paso en ms (0 = sin timeout)
depends no [] Pasos que deben completarse antes

Los campos adicionales dependen del action y los define cada app/dominio.

Actions por dominio

Cada app registra sus actions validos. Ejemplos:

Dominio Actions App de referencia
navegacion navigate, click, type, wait, screenshot, evaluate, get_html, wait_load, sleep script_navegador
shell command, script dag_engine / apps genericas
registry function, args dag_engine (invoca fn run <id> y captura function_id en dag_step_results)
database query, migrate, backup futuras apps
http request, assert_status, extract futuras apps

2. Exit Codes

Toda app/script que siga este estandar DEBE salir con uno de estos tres codigos:

Codigo Constante Significado
0 EXIT_SUCCESS Todos los pasos completaron sin error
1 EXIT_FAILURE Al menos un paso fallo y el flujo aborto
2 EXIT_PARTIAL Algunos pasos fallaron pero el flujo continuo (continue_on_error)

Reglas

  • Si todos los pasos son exitosos → 0
  • Si algun paso falla y tiene continue_on_error: false1 (aborta)
  • Si algun paso falla pero todos los fallos tenian continue_on_error: true2
  • Un timeout agotado cuenta como fallo del paso

Compatibilidad con dag_engine

dag_engine interpreta exit code != 0 como fallo. Para que 2 (partial) no dispare el handler de failure, el DAG que llama a la app puede usar continue_on.exit_code:

steps:
  - name: run_app
    command: ./mi_app --script flujo.yaml
    continue_on:
      exit_code: [2]   # exit 2 no aborta el DAG (partial es tolerado)
  - name: check_result
    command: test $? -le 1  # falla solo si fue error fatal
    depends: [run_app]

O alternativamente, la app puede configurarse con --strict para mapear partial → failure (exit 1).


3. Output Estructurado

Toda app DEBE escribir un resumen JSON en stdout al finalizar (despues de su output humano). El formato:

{
  "name": "nombre_del_flujo",
  "status": "success|failure|partial",
  "exit_code": 0,
  "started_at": "2026-04-01T10:00:00Z",
  "ended_at": "2026-04-01T10:00:05Z",
  "duration_ms": 5000,
  "steps_total": 5,
  "steps_ok": 4,
  "steps_failed": 1,
  "steps": [
    {
      "name": "paso_1",
      "action": "navigate",
      "status": "ok",
      "elapsed_ms": 1200,
      "output": "optional output"
    },
    {
      "name": "paso_2",
      "action": "click",
      "status": "error",
      "elapsed_ms": 500,
      "error": "elemento no encontrado: #btn-submit"
    }
  ]
}

Separacion humano / maquina

  • stderr: logs legibles para humanos (progreso, warnings)
  • stdout: output JSON estructurado al final (para dag_engine, hooks, pipes)
  • Flag --json o --quiet: suprime output humano, solo JSON en stdout

Esto permite que dag_engine capture el JSON:

steps:
  - name: navegacion
    command: ./script_navegador --script busqueda.yaml --json
    output: RESULT
  - name: analizar
    command: echo "$RESULT" | jq '.steps[] | select(.status=="error")'
    depends: [navegacion]

4. Hooks

Los hooks se ejecutan despues de todos los pasos, segun el exit code resultante.

En el YAML del flujo

hooks:
  on_success: "notify-send 'Flujo completado'"
  on_failure: "echo 'FALLO: {{.Name}}' >> /tmp/failures.log"
  on_partial: "echo 'PARCIAL: {{.StepsFailed}} pasos fallaron' >> /tmp/warnings.log"
  on_step_error: "echo 'Step {{.StepName}} fallo: {{.Error}}'"  # se ejecuta por cada paso fallido

Variables disponibles en hooks

Variable Tipo Descripcion
{{.Name}} string Nombre del flujo
{{.Status}} string success, failure, partial
{{.ExitCode}} int 0, 1, 2
{{.DurationMs}} int Duracion total en ms
{{.StepsTotal}} int Total de pasos
{{.StepsOk}} int Pasos exitosos
{{.StepsFailed}} int Pasos fallidos
{{.StepName}} string Nombre del paso (solo en on_step_error)
{{.Error}} string Mensaje de error (solo en on_step_error/on_failure)

En dag_engine (handlers globales)

Los hooks del YAML son locales al flujo. Para orquestacion, dag_engine maneja sus propios handlers (init/success/failure/exit, alias handler_on):

# apps/dag_engine/dags_migrated/mi_automatizacion.yaml
name: mi_automatizacion
handlers:
  failure:
    - name: alerta
      command: echo "Fallo en mi_automatizacion" >> /var/log/fn_registry/failures.log
  success:
    - name: registrar
      command: echo "OK $(date)" >> /var/log/fn_registry/success.log
steps:
  - name: ejecutar
    function: mi_pipeline_bash_pipelines
    args: ["flujo.yaml"]

5. Integracion con operations.db

Las apps que siguen el bucle reactivo DEBEN registrar la ejecucion en operations.db:

Mapping del estandar a operations.db

Concepto estandar Tabla operations.db Campo
Flujo completo executions status: success/failure/partial
Cada paso logs level: info/error, message: resultado
Exit code executions.metrics {"exit_code": N}
Output JSON executions.metrics {"steps": [...]}
Duracion executions.duration_ms milisegundos
Pasos in/out executions.records_in/out total/exitosos

Patron de registro (referencia: script_navegador/ops.go)

1. initOpsDB()           → abrir/crear operations.db
2. EnsureEntities()      → registrar recursos involucrados
3. EnsureRelations()     → registrar como se conectan
4. [ejecutar flujo]
5. RecordRun()           → insertar execution con metricas
6. LogStep() por paso    → insertar log por cada paso
7. UpdateRelation()      → status final de la relation

Este patron ya esta implementado en script_navegador/ops.go y sirve como referencia para nuevas apps.


6. Flujo completo: App → dag_engine → Hooks

                  dag_engine (scheduler)
                         │
                    cron / manual / API
                         │
                    ┌────▼────┐
                    │  DAG    │  apps/dag_engine/dags_migrated/mi_dag.yaml
                    │  step   │  command: ./mi_app --script flujo.yaml --json
                    └────┬────┘
                         │
                    ┌────▼────────────────┐
                    │  App (mi_app)       │
                    │                     │
                    │  1. Load YAML       │
                    │  2. Validate steps  │
                    │  3. Init ops.db     │
                    │  4. Execute steps   │
                    │  5. Run hooks       │
                    │  6. Record to ops   │
                    │  7. JSON → stdout   │
                    │  8. Exit code       │
                    └────┬────────────────┘
                         │
              ┌──────────┼──────────┐
              │          │          │
         exit 0     exit 1     exit 2
         success    failure    partial
              │          │          │
              ▼          ▼          ▼
       engine OK   engine FAIL  engine FAIL*
       handler     handler      (configurable)

*Con continue_on: { exit_code: [2] } en el step, exit 2 no aborta el DAG.


7. Implementacion: funciones del registry

Existentes (ya en uso por script_navegador)

Todas las funciones CDP del registry (cdp_*_go_browser, chrome_launch_go_browser) ya siguen el patron de retornar error. El runner de script_navegador las compone.

Nuevas (por construir)

Funcion Lang Domain Descripcion
run_steps bash shell Ejecuta pasos de un YAML generico (action=command/script), reporta JSON y sale con 0/1/2
report_execution_json bash shell Genera el JSON de salida estandar dado los resultados de pasos
exit_with_status bash shell Calcula exit code (0/1/2) a partir de contadores ok/fail/partial

Estas funciones Bash permiten que cualquier script nuevo siga el estandar sin reimplementar la logica de reporte y exit codes.


8. Ejemplo completo

Script YAML (flujo.yaml)

name: backup_db
description: "Backup de operations.db y push a remoto"
steps:
  - name: check_db
    action: command
    command: "test -f operations.db"
  - name: backup
    action: command
    command: "cp operations.db backups/operations_$(date +%Y%m%d).db"
    depends: [check_db]
  - name: push
    action: command
    command: "git add backups/ && git commit -m 'backup' && git push"
    depends: [backup]
    continue_on_error: true
hooks:
  on_failure: "echo 'Backup fallo' >> /tmp/backup_errors.log"

Invocacion desde dag_engine

name: backup_diario
schedule: "0 2 * * *"
working_dir: /home/lucas/fn_registry/apps/mi_app
steps:
  - name: backup
    function: backup_db_bash_pipelines
    args: ["flujo.yaml", "--json"]
handlers:
  failure:
    - name: alerta
      command: echo "Backup fallo $(date)" >> /var/log/fn_registry/failures.log

Output esperado (stdout)

{
  "name": "backup_db",
  "status": "partial",
  "exit_code": 2,
  "started_at": "2026-04-01T02:00:00Z",
  "ended_at": "2026-04-01T02:00:03Z",
  "duration_ms": 3000,
  "steps_total": 3,
  "steps_ok": 2,
  "steps_failed": 1,
  "steps": [
    {"name": "check_db", "action": "command", "status": "ok", "elapsed_ms": 10},
    {"name": "backup", "action": "command", "status": "ok", "elapsed_ms": 450},
    {"name": "push", "action": "command", "status": "error", "elapsed_ms": 2540, "error": "remote rejected"}
  ]
}

Exit code: 2 (partial — push fallo pero tenia continue_on_error).


Resumen del contrato

Aspecto Regla
Formato de flujo YAML con name + steps[]
Exit codes 0=success, 1=failure, 2=partial
Output maquina JSON en stdout (con --json)
Output humano stderr (progreso, warnings)
Error handling continue_on_error por paso, hooks por resultado
Trazabilidad operations.db (executions + logs)
Orquestacion dag_engine como scheduler (apps/dag_engine/), apps/funciones del registry como ejecutores
Funciones Reutilizar del registry, actions por dominio