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>
This commit is contained in:
@@ -140,7 +140,7 @@ Cobertura por capa, no todas activas a la vez:
|
||||
### Que NO se monitoriza
|
||||
|
||||
- Funcion Go/C++ llamada internamente por app ya compilada.
|
||||
- Funcion ejecutada por systemd timer / cron / Dagu sin pasar por `fn run`.
|
||||
- Funcion ejecutada por systemd timer / cron / dag_engine **step `command:`** (no `function:`) sin pasar por `fn run`. Nota: dag_engine steps con `function:` SI quedan trazados — el executor invoca `fn run <id>` y guarda `function_id` en `dag_step_results`.
|
||||
- Sub-agente (`Agent` tool) — sus tools no propagan a hook del padre.
|
||||
- Service de produccion recibiendo HTTP.
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# Propuesta e2e_checks para apps/auto_metabase
|
||||
#
|
||||
# Generado por fn-recopilador en modo design-e2e
|
||||
# App: auto_metabase (lang: py, framework: httpx, entry_point: main.py)
|
||||
# Detectado:
|
||||
# - CLI con argparse, subcomandos (projects, login, pull, push, validate, ...)
|
||||
# - NO service (no tag 'service', no puerto propio)
|
||||
# - Deps: yaml, httpx en python/.venv (registry venv compartido)
|
||||
# - Test: dashboard_split_test.py — pytest puro, imports relativos a apps/auto_metabase/
|
||||
# - NO operations.db propia (no bucle reactivo propio)
|
||||
# - Toca Metabase REAL en todos los comandos de red (login, pull, push, ...)
|
||||
# -> todos los checks e2e deben ser OFF-LINE: import, CLI help, tests puros
|
||||
#
|
||||
# Justificacion por check:
|
||||
#
|
||||
# | check | razon |
|
||||
# |--------------------|---------------------------------------------------------------------------|
|
||||
# | import | Valida que el entry point y sus modulos cargan sin errores de import, |
|
||||
# | | que sys.path al registry de funciones esta bien y que yaml/httpx estan |
|
||||
# | | disponibles en el venv compartido. |
|
||||
# | cli_help | Valida que argparse esta correctamente configurado y que el CLI no |
|
||||
# | | crashea en el arranque (antes de que necesite project o red). Confirma |
|
||||
# | | que todos los submodulos (sync_push, sync_pull, payload, ...) se cargan. |
|
||||
# | mbql_validate_pure | Valida la funcion PURA metabase_mbql_validate del registry sin tocar |
|
||||
# | | red. Es la unica funcion del modulo que puede correr offline sin |
|
||||
# | | credenciales. Detecta regresiones en el validador MBQL local. |
|
||||
# | tests_dashboard_split | Corre dashboard_split_test.py — tests puros del helper de split/merge |
|
||||
# | | de dashboards por tab. No requieren red ni credenciales. |
|
||||
# Los checks que SI requieren red (login, pull, push, validate --check-sql)
|
||||
# no se incluyen porque tocan Metabase real o credenciales. Se dejan para
|
||||
# un entorno de smoke con Metabase en Docker (ver nota abajo).
|
||||
#
|
||||
# NOTA — smoke con Docker (severity: warning, no incluido por default):
|
||||
# Si se quiere gate de smoke real, levantar Metabase en Docker local y usar:
|
||||
# docker run -d -p 3100:3000 --name mb_e2e metabase/metabase
|
||||
# python/.venv/bin/python3 apps/auto_metabase/main.py init-project e2e_test --base-url http://localhost:3100 \
|
||||
# --email test@test.com --password Test1234!
|
||||
# python/.venv/bin/python3 apps/auto_metabase/main.py -p e2e_test login
|
||||
# python/.venv/bin/python3 apps/auto_metabase/main.py -p e2e_test status
|
||||
# Ese bloque queda fuera de e2e_checks hasta tener un entorno Docker reproducible.
|
||||
|
||||
app_id: auto_metabase
|
||||
dir_path: apps/auto_metabase
|
||||
lang: py
|
||||
generated_by: fn-recopilador
|
||||
generated_at: "2026-05-19"
|
||||
|
||||
e2e_checks:
|
||||
- id: import
|
||||
# Carga completa del entry point: sys.path al registry, yaml, httpx,
|
||||
# metabase.client, payload, sync_pull, sync_push, etc.
|
||||
# Si cualquier modulo tiene un import roto, falla aqui.
|
||||
cmd: >-
|
||||
python/.venv/bin/python3 -c
|
||||
'import sys; sys.path.insert(0, "apps/auto_metabase");
|
||||
import importlib.util, pathlib;
|
||||
spec = importlib.util.spec_from_file_location("main", "apps/auto_metabase/main.py");
|
||||
m = importlib.util.module_from_spec(spec);
|
||||
spec.loader.exec_module(m);
|
||||
print("import OK")'
|
||||
expect_stdout_contains: "import OK"
|
||||
timeout_s: 30
|
||||
severity: critical
|
||||
# por que: valida que el entry point carga sin ImportError (yaml, httpx,
|
||||
# metabase.client, payload, sync_push, ...) usando el venv del registro.
|
||||
|
||||
- id: cli_help
|
||||
# Valida que argparse esta bien formado y todos los submodulos cargan.
|
||||
# 'projects --help' dispara solo el parser, no requiere red ni proyecto.
|
||||
cmd: "python/.venv/bin/python3 apps/auto_metabase/main.py --help"
|
||||
expect_stdout_contains: "usage:"
|
||||
timeout_s: 15
|
||||
severity: critical
|
||||
# por que: el CLI tiene ~30 subcomandos; si cualquier modulo falla al
|
||||
# importarse (payload.py, sync_push.py, etc.) esto lo captura.
|
||||
|
||||
- id: mbql_validate_pure
|
||||
# Corre la funcion PURA metabase_mbql_validate del registry de forma offline.
|
||||
# Prueba un dataset_query MBQL minimo valido (debe devolver lista vacia de errores)
|
||||
# y uno con UUID duplicado (debe detectar el error).
|
||||
cmd: >-
|
||||
python/.venv/bin/python3 - <<'PYEOF'
|
||||
import sys
|
||||
sys.path.insert(0, "python/functions")
|
||||
from metabase import metabase_mbql_validate
|
||||
import uuid
|
||||
|
||||
# Caso valido: query nativo minimo sin errores
|
||||
u1, u2 = str(uuid.uuid4()), str(uuid.uuid4())
|
||||
valid_q = {
|
||||
"lib/type": "mbql/query",
|
||||
"database": 1,
|
||||
"stages": [{"lib/type": "mbql.stage/native",
|
||||
"native": "SELECT 1",
|
||||
"lib/uuid": u1}],
|
||||
"lib/uuid": u2,
|
||||
}
|
||||
errs = metabase_mbql_validate(valid_q)
|
||||
assert errs == [], f"Esperaba 0 errores, got: {errs}"
|
||||
|
||||
# Caso invalido: UUID duplicado
|
||||
dup = str(uuid.uuid4())
|
||||
bad_q = {
|
||||
"lib/type": "mbql/query",
|
||||
"database": 1,
|
||||
"stages": [{"lib/type": "mbql.stage/native",
|
||||
"native": "SELECT 1",
|
||||
"lib/uuid": dup}],
|
||||
"lib/uuid": dup,
|
||||
}
|
||||
errs2 = metabase_mbql_validate(bad_q)
|
||||
assert len(errs2) > 0, "Esperaba error de UUID duplicado"
|
||||
|
||||
print("mbql_validate_pure OK")
|
||||
PYEOF
|
||||
expect_stdout_contains: "mbql_validate_pure OK"
|
||||
timeout_s: 30
|
||||
severity: critical
|
||||
# por que: metabase_mbql_validate es la unica funcion del modulo metabase
|
||||
# ejecutable offline. Detecta regresiones en el validador MBQL puro.
|
||||
|
||||
- id: tests_dashboard_split
|
||||
# Corre el unico test file puro de la app: dashboard_split_test.py.
|
||||
# No requiere red, no toca Metabase. Pytest con confdir en la app dir.
|
||||
cmd: >-
|
||||
python/.venv/bin/python3 -m pytest -x -q
|
||||
--rootdir=apps/auto_metabase
|
||||
apps/auto_metabase/dashboard_split_test.py
|
||||
timeout_s: 60
|
||||
severity: critical
|
||||
# por que: dashboard_split.py tiene logica de split/merge de dashboards
|
||||
# multi-tab y el test file tiene casos exhaustivos. Es el unico gate
|
||||
# automatico de esa logica que no requiere Metabase.
|
||||
@@ -0,0 +1,43 @@
|
||||
# app_id: deploy_server
|
||||
# lang: go (CGO, sin fts5)
|
||||
# stack: net/http + go-sqlite3 (CGO) + multi-strategy deployer (systemd/systemd-remote/docker-compose)
|
||||
# deteccion automatica fn-recopilador 2026-05-19, issue 0121a
|
||||
#
|
||||
# Notas de deteccion:
|
||||
# - go.mod usa github.com/mattn/go-sqlite3 (CGO=1) pero NO usa fts5 tags
|
||||
# - sin *_test.go en el directorio -> check tests OMITIDO
|
||||
# - server.go confirma: --port flag con default 9090, GET /api/health handler
|
||||
# - puerto e2e: 9190 (+100 sobre puerto productivo 9090)
|
||||
# - operations.db presente con tablas propias (deploy_targets, deploy_logs) -> ops_audit incluido
|
||||
# - BD e2e en /tmp/ para idempotencia; binario en /tmp/ para no contaminar el directorio de la app
|
||||
|
||||
e2e_checks:
|
||||
# build: compila el binario con CGO habilitado. Sin -tags fts5 (no lo usa el modulo).
|
||||
# Valida que el modulo go-sqlite3 linkea correctamente en el entorno local.
|
||||
- id: build
|
||||
cmd: "cd /home/lucas/fn_registry/apps/deploy_server && CGO_ENABLED=1 go build -o /tmp/deploy_server_e2e_bin ."
|
||||
timeout_s: 180
|
||||
|
||||
# cli_help: verifica que el binario arranca y responde sin crashear ni pedir args obligatorios.
|
||||
# Captura el caso de init() con side effects o missing assets.
|
||||
- id: cli_help
|
||||
cmd: "/tmp/deploy_server_e2e_bin help"
|
||||
expect_exit: 0
|
||||
expect_stdout_contains: "deploy_server"
|
||||
timeout_s: 10
|
||||
|
||||
# migrations: arranca el servidor con la BD de prueba y lo cierra inmediatamente.
|
||||
# El servidor abre la BD al arrancar y aplica migraciones automaticamente (sqlite_apply_migrations).
|
||||
# Usamos --port 0 no esta soportado (flag es int sin port-0), asi que usamos puerto alto efimero.
|
||||
# Tras el health check, el proceso se mata automaticamente por fn-analizador.
|
||||
- id: smoke
|
||||
cmd: "/tmp/deploy_server_e2e_bin serve --port 9190 --db /tmp/deploy_server_e2e.db &"
|
||||
health: "http://127.0.0.1:9190/api/health"
|
||||
timeout_s: 15
|
||||
|
||||
# ops_audit: fn-recopilador audita operations.db de la app.
|
||||
# Verifica integridad referencial, schema completo, ausencia de datos corruptos.
|
||||
# La BD tiene tablas propias (deploy_targets, deploy_logs) + tablas del ciclo reactivo.
|
||||
- id: ops_audit
|
||||
ref: "fn-recopilador:apps/deploy_server"
|
||||
severity: warning
|
||||
@@ -0,0 +1,116 @@
|
||||
# e2e_checks proposal — registry_api
|
||||
#
|
||||
# app_id: registry_api
|
||||
# lang: go
|
||||
# stack: net/http, CGO+FTS5, SQLite (mattn/go-sqlite3), replace fn-registry => ../../
|
||||
# date: 2026-05-19
|
||||
# issue: 0121 (design-e2e fn-recopilador)
|
||||
#
|
||||
# Diagnostico del stack:
|
||||
# - entry_point: main.go (acepta --port N y --db PATH)
|
||||
# - health endpoint: GET /api/status (sin auth, siempre 200 + JSON {"status":"ok",...})
|
||||
# - auth layer: checkToken() en handleSync solamente; Traefik basicAuth en prod (no
|
||||
# aplica en e2e local). /api/status, /api/search, /api/locations NO tienen auth.
|
||||
# - no hay *_test.go => check "tests" omitido
|
||||
# - operations.db no declarada para esta app => ops_audit omitido
|
||||
# - puerto prod 8420 => e2e usa 8521 para no colisionar
|
||||
#
|
||||
# Instrucciones de adopcion:
|
||||
# 1. Copiar el bloque "e2e_checks:" al frontmatter de apps/registry_api/app.md
|
||||
# (justo antes del primer "##" de la seccion de prosa).
|
||||
# 2. Revisar que el binario compilado quede en apps/registry_api/registry_api
|
||||
# (el build check lo deja ahi por convencian).
|
||||
# 3. El check auth_check requiere que REGISTRY_API_TOKEN este seteada en el
|
||||
# entorno de test; si no lo esta, /api/sync devuelve 200 (token desactivado).
|
||||
# Ajustar severity a "warning" si el CI no tiene la variable disponible.
|
||||
#
|
||||
# NOTA: NO escribir directo al app.md — propuesta para revision humana.
|
||||
|
||||
e2e_checks:
|
||||
# --- build ---
|
||||
# Compila el binario localmente con CGO+FTS5. El replace directive en go.mod
|
||||
# apunta a ../../ (raiz del registry), por lo que el build debe lanzarse
|
||||
# desde dentro del directorio de la app.
|
||||
- id: build
|
||||
cmd: "cd /home/lucas/fn_registry/apps/registry_api && CGO_ENABLED=1 go build -tags fts5 -o registry_api ."
|
||||
timeout_s: 120
|
||||
severity: critical
|
||||
# por que: sin binario el resto de checks no tiene sentido; fallo de build
|
||||
# indica cambio de API en fn-registry (replace) o dep rota.
|
||||
|
||||
# --- smoke ---
|
||||
# Arranca el servidor con una BD efimera en /tmp y espera que responda en
|
||||
# /api/status. Puerto 8521 (!=8420 prod, !=8420 dev) para no colisionar.
|
||||
# El proceso en background se mata al terminar la suite por fn-analizador.
|
||||
- id: smoke
|
||||
cmd: "/home/lucas/fn_registry/apps/registry_api/registry_api -port 8521 -db /tmp/registry_api_e2e.db &"
|
||||
health: "http://127.0.0.1:8521/api/status"
|
||||
timeout_s: 10
|
||||
severity: critical
|
||||
# por que: tag 'service' => gate minimo es que el binario arranca y sirve
|
||||
# el health endpoint. Sin esto no tiene sentido probar endpoints de negocio.
|
||||
|
||||
# --- status_json ---
|
||||
# Verifica que /api/status devuelve JSON con campo "status":"ok".
|
||||
# Distingue un servidor arrancado de uno en modo degradado.
|
||||
- id: status_json
|
||||
cmd: "curl -sf http://127.0.0.1:8521/api/status"
|
||||
expect_stdout_contains: '"status":"ok"'
|
||||
timeout_s: 5
|
||||
severity: critical
|
||||
# por que: el health check del smoke solo valida HTTP 200; este check
|
||||
# valida ademas el contrato de respuesta JSON.
|
||||
|
||||
# --- search_open ---
|
||||
# Verifica que /api/search acepta GET sin auth y devuelve JSON valido.
|
||||
# Usa q=test para tener un resultado predecible (siempre devuelve arrays).
|
||||
- id: search_open
|
||||
cmd: "curl -sf 'http://127.0.0.1:8521/api/search?q=test'"
|
||||
expect_stdout_contains: '"functions"'
|
||||
timeout_s: 5
|
||||
severity: critical
|
||||
# por que: /api/search es el endpoint mas usado por fn sync y el MCP.
|
||||
# Fallo aqui indica problema de FTS5 o schema de registry.db.
|
||||
|
||||
# --- search_missing_q ---
|
||||
# Verifica que /api/search sin q= devuelve 400 (validacion de entrada).
|
||||
- id: search_missing_q
|
||||
cmd: "curl -s -o /dev/null -w '%{http_code}' 'http://127.0.0.1:8521/api/search'"
|
||||
expect_stdout_contains: "400"
|
||||
timeout_s: 5
|
||||
severity: warning
|
||||
# por que: regression guard para la validacion de parametros obligatorios.
|
||||
|
||||
# --- auth_check ---
|
||||
# Verifica que POST /api/sync rechaza requests con token incorrecto cuando
|
||||
# REGISTRY_API_TOKEN esta seteada. Usa credencial falsa conocida.
|
||||
# IMPORTANTE: si REGISTRY_API_TOKEN no esta en el entorno de e2e, el servidor
|
||||
# responde 200 (token desactivado por diseno). En ese caso este check pasa
|
||||
# igualmente (exit 0 de curl) pero no valida auth real — ajustar a warning
|
||||
# o setear la variable antes de correr la suite.
|
||||
- id: auth_check
|
||||
cmd: >
|
||||
REGISTRY_API_TOKEN=real-secret
|
||||
/home/lucas/fn_registry/apps/registry_api/registry_api -port 8522 -db /tmp/registry_api_e2e_auth.db &
|
||||
sleep 1 &&
|
||||
STATUS=$(curl -s -o /dev/null -w '%{http_code}'
|
||||
-X POST http://127.0.0.1:8522/api/sync
|
||||
-H 'Content-Type: application/json'
|
||||
-H 'X-Registry-Token: wrong-token'
|
||||
-d '{"pc_id":"e2e"}') &&
|
||||
kill %1 2>/dev/null; [ "$STATUS" = "401" ]
|
||||
timeout_s: 15
|
||||
severity: warning
|
||||
# por que: /api/sync es el unico endpoint protegido por token de aplicacion.
|
||||
# Fallo indica regresion en checkToken(). Severity warning porque en CI sin
|
||||
# REGISTRY_API_TOKEN el comportamiento es abierto por diseno.
|
||||
|
||||
# --- locations_open ---
|
||||
# Verifica que /api/locations devuelve JSON sin requerir auth.
|
||||
- id: locations_open
|
||||
cmd: "curl -sf http://127.0.0.1:8521/api/locations"
|
||||
expect_stdout_contains: "["
|
||||
timeout_s: 5
|
||||
severity: warning
|
||||
# por que: /api/locations es consumido por fn sync locations; fallo indica
|
||||
# problema en db.ListAllPcLocations() o schema de pc_locations.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Propuesta e2e_checks para apps/shaders_lab
|
||||
# Generado por fn-recopilador modo design-e2e
|
||||
# Fecha: 2026-05-19
|
||||
#
|
||||
# Diagnostico:
|
||||
# lang=cpp, framework=imgui (fn::run_app), domain=gfx
|
||||
# toolchain: mingw-w64 (cross-compile Windows desde WSL)
|
||||
# modulos: main.cpp + compiler.cpp + 19 .cpp del registry (gfx + core)
|
||||
# deps externas: imgui_node_editor, SQLite::SQLite3
|
||||
# persistencia propia: shaders_lab.db (junto al .exe, via shaderlab_db)
|
||||
# sin tests/ ni tests_*.py detectados
|
||||
# sin primitives_gallery integration (golden_image: OMITIDO)
|
||||
# sin --self-test: fn::run_app no parsea argv (confirmado piloto 0120)
|
||||
# sin tag 'service': no expone HTTP (smoke con health: OMITIDO)
|
||||
# operations.db: NO usa (ops_audit: OMITIDO)
|
||||
#
|
||||
# Patron: C++ ImGui — checks estructurales sin lanzar GUI ni GPU
|
||||
|
||||
app_id: shaders_lab
|
||||
|
||||
e2e_checks:
|
||||
# Build: compila el target completo para Windows via mingw-w64.
|
||||
# Valida que todos los .cpp del registry enlazados (19 archivos gfx+core)
|
||||
# compilan sin errores y el linkado con imgui_node_editor + SQLite tiene exito.
|
||||
# Es el check mas valioso: detecta regresiones de API en cualquiera de las
|
||||
# 19 funciones del registry que usa la app.
|
||||
- id: build
|
||||
cmd: "cmake --build /home/lucas/fn_registry/cpp/build/windows --target shaders_lab -j"
|
||||
timeout_s: 300
|
||||
severity: critical
|
||||
|
||||
# Verifica que el artefacto .exe existe tras el build.
|
||||
# Detecta casos donde cmake reporta exit 0 pero el linker no produjo binario
|
||||
# (muy raro con mingw-w64 pero ha ocurrido en builds parciales con -j).
|
||||
- id: binary_exists
|
||||
cmd: "test -f /home/lucas/fn_registry/cpp/build/windows/apps/shaders_lab/shaders_lab.exe"
|
||||
timeout_s: 5
|
||||
severity: critical
|
||||
|
||||
# Verifica que el .ico esta presente junto al .exe.
|
||||
# add_imgui_app genera shaders_lab_appicon.rc que windres incluye como recurso;
|
||||
# si el .ico falta el build pasa pero el .exe queda sin icono embebido
|
||||
# (visible al deploy a /mnt/c/.../Desktop/apps/).
|
||||
- id: icon_exists
|
||||
cmd: "test -f /home/lucas/fn_registry/apps/shaders_lab/appicon.ico"
|
||||
timeout_s: 5
|
||||
severity: warning
|
||||
@@ -39,6 +39,7 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
|
||||
| [navegator](navegator.md) | 4 | Automatización de browser via CDP + AX tree + LLM: obtener, limpiar, chunkear AX tree y llamar a Claude CLI |
|
||||
| [cpp-dashboard-viz](cpp-dashboard-viz.md) | 10 | Primitivas C++ ImGui para dashboards: kpi_card, sparkline, line/bar/scatter/pie/heatmap/histogram, panel containers |
|
||||
| [agents](agents.md) | 3 | Orquestar agentes Claude headless en git worktrees: launch, cleanup, DoD evidence schema audit |
|
||||
| [backends](backends.md) | — | Stacks backend (Go net/http+SQLite default, MCP, mautrix, bubbletea, httpx, docker-compose): decision tree + esqueleto canonico + funciones del registry a componer |
|
||||
|
||||
## Como anadir grupo
|
||||
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
# Capability: backends
|
||||
|
||||
Catalogo de stacks backend usados en `apps/`. Para que un agente que va a construir un backend nuevo sepa **que stack elegir**, **que funciones del registry componer** y **que esqueleto copiar** sin reinventar nada.
|
||||
|
||||
No es un grupo `tag:` del registry — es una guia transversal (los stacks atraviesan dominios). Complementa `cpp_apps.md` (frontend C++) y `deploy.md` (entrega).
|
||||
|
||||
## Stacks soportados
|
||||
|
||||
| Stack | Lang | Cuando elegir | Apps de referencia |
|
||||
|---|---|---|---|
|
||||
| **Go net/http stdlib + SQLite** | go | **Default.** API HTTP local o detras de Traefik. Persistencia SQLite con migraciones embed.FS. | `sqlite_api`, `services_api`, `registry_api`, `kanban`, `deploy_server`, `dag_engine`, `agent_runner_api`, `call_monitor` |
|
||||
| **Go MCP stdio/http** | go | Tool server para Claude/agentes. Lectura/escritura registry. | `registry_mcp` |
|
||||
| **Go bubbletea TUI** | go | CLI interactiva sin HTTP. Pipeline launcher, docker manager. | `pipeline_launcher`, `docker_tui`, `dev_console` |
|
||||
| **Go mautrix bot** | go | Bot Matrix con E2EE, LLM tools, comandos. | `agents_and_robots` |
|
||||
| **Python httpx + YAML** | py | Cliente declarativo de API REST externa (Metabase, Gitea, etc.). Pull/push contra disco. | `auto_metabase`, `metabase_registry` |
|
||||
| **Bash docker-compose** | bash | Stack multi-container (Synapse + Element + LiveKit, PostGIS + Valhalla). | `element_matrix_chat`, `footprint_geo_stack` |
|
||||
| **C++ ImGui frontend** | cpp | NO es backend — cliente HTTP/WS de los Go services. Ver `cpp_apps.md`. | `services_monitor`, `registry_dashboard`, ... |
|
||||
|
||||
## Decision tree
|
||||
|
||||
```
|
||||
¿Necesitas HTTP API?
|
||||
├─ si → ¿Es para Claude/agentes?
|
||||
│ ├─ si → MCP server (Go) → copiar `registry_mcp`
|
||||
│ └─ no → Go net/http stdlib + SQLite + embed.FS migrations → copiar `services_api`
|
||||
│
|
||||
└─ no → ¿Interactivo terminal?
|
||||
├─ si → Go bubbletea → copiar `pipeline_launcher`
|
||||
└─ no → ¿Cliente de API externa?
|
||||
├─ si → Python httpx → copiar `auto_metabase`
|
||||
└─ no → ¿Stack de containers?
|
||||
├─ si → Bash + docker-compose.yml → copiar `element_matrix_chat`
|
||||
└─ no → reevalua, probablemente sea funcion del registry no app
|
||||
```
|
||||
|
||||
## Esqueleto canonico: Go net/http stdlib + SQLite (el default)
|
||||
|
||||
### Layout
|
||||
|
||||
```
|
||||
apps/<name>/
|
||||
app.md # frontmatter + service: block (issue 0105)
|
||||
main.go # flags + listener + signal handling
|
||||
server.go # http.ServeMux + routes + middleware chain
|
||||
db.go # sql.Open con WAL + FK + apply migrations
|
||||
handlers_*.go # 1 archivo por recurso
|
||||
migrations/
|
||||
001_init.sql # CREATE TABLE IF NOT EXISTS (idempotente)
|
||||
002_*.sql # aditivo. NUNCA modificar 001 ya commiteado
|
||||
operations.db # SQLite local. Gitignored.
|
||||
```
|
||||
|
||||
### Frontmatter app.md
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my_service
|
||||
lang: go
|
||||
domain: infra # ver list_domains
|
||||
version: 0.1.0
|
||||
description: "1 linea: que hace + por que existe."
|
||||
tags: [service, api, http, sqlite]
|
||||
uses_functions:
|
||||
- sqlite_apply_versioned_migrations_go_infra
|
||||
- http_serve_go_infra
|
||||
- http_json_response_go_infra
|
||||
- http_parse_body_go_infra
|
||||
- http_router_go_infra
|
||||
- http_cors_middleware_go_infra
|
||||
- http_logger_middleware_go_infra
|
||||
- logger_go_infra
|
||||
uses_types: []
|
||||
framework: "net/http"
|
||||
entry_point: "main.go"
|
||||
dir_path: "apps/my_service"
|
||||
service: # OBLIGATORIO si tag service. issue 0105
|
||||
port: 8500
|
||||
health_endpoint: /api/health
|
||||
health_timeout_s: 3
|
||||
systemd_unit: my_service.service
|
||||
systemd_scope: user
|
||||
restart_policy: always # NUNCA on-failure (ver gotcha cpp_apps.md)
|
||||
runtime: systemd-user
|
||||
pc_targets: [home-wsl]
|
||||
is_local_only: false
|
||||
---
|
||||
```
|
||||
|
||||
### main.go (minimo viable)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bind := flag.String("bind", "127.0.0.1:8500", "addr to listen on")
|
||||
dbPath := flag.String("db", "operations.db", "sqlite path")
|
||||
flag.Parse()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
db, err := openDB(*dbPath) // db.go — usa sqlite_apply_versioned_migrations
|
||||
if err != nil { log.Fatal(err) }
|
||||
defer db.Close()
|
||||
|
||||
srv := newServer(db) // server.go — http.ServeMux + rutas
|
||||
log.Printf("listening on %s", *bind)
|
||||
if err := serve(ctx, *bind, srv); err != nil { log.Fatal(err) }
|
||||
}
|
||||
```
|
||||
|
||||
### Build + service unit
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=1 go build -tags fts5 -o my_service .
|
||||
systemctl --user enable --now my_service.service # tras generar unit
|
||||
```
|
||||
|
||||
## Esqueleto canonico: MCP server Go
|
||||
|
||||
Copia `registry_mcp`:
|
||||
- `main.go` parsea flags (`--stdio` o `--http`).
|
||||
- Cada tool = funcion Go con schema JSON y handler.
|
||||
- Schema generation con `jsonschema` reflect.
|
||||
- Read-only por default; `--enable-run` / `--enable-write` gating.
|
||||
|
||||
Funciones del registry relevantes: cualquier de `mcp__registry__fn_*` para entender shape. Patron de gating en `apps/registry_mcp/main.go`.
|
||||
|
||||
## Esqueleto canonico: Python httpx cliente declarativo
|
||||
|
||||
Copia `auto_metabase`:
|
||||
- `python/.venv/bin/python3` como interprete.
|
||||
- Importa wrappers del registry (`from infra import metabase_auth, metabase_get_dashboard, ...`).
|
||||
- YAML manifest define estado deseado.
|
||||
- Pull = volcar a YAML. Push = enviar a API.
|
||||
- NUNCA `requests.post(...)` directo — siempre via wrapper que ya hace auth + retry + telemetria.
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python", "functions"))
|
||||
from infra import metabase_auth, metabase_list_dashboards
|
||||
session = metabase_auth(host, user, pwd)
|
||||
for d in metabase_list_dashboards(session):
|
||||
...
|
||||
```
|
||||
|
||||
## Esqueleto canonico: bubbletea TUI
|
||||
|
||||
Copia `pipeline_launcher`:
|
||||
- `tea.Model` con `Init/Update/View`.
|
||||
- Sin HTTP. Estado local + acciones sobre disco/registry.
|
||||
- Tag `launcher` si debe aparecer en el Pipeline Launcher (function_tags.md).
|
||||
|
||||
## Esqueleto canonico: Bash docker-compose stack
|
||||
|
||||
Copia `element_matrix_chat`:
|
||||
- `docker-compose.yml` + `.env` + scripts `up.sh`/`down.sh`.
|
||||
- Sin codigo Go/Py — solo composicion de imagenes.
|
||||
- Tag `service` + `runtime: docker-compose` en `service:`.
|
||||
- Deploy via `docker_compose_remote_deploy_bash_infra` (ver capability `deploy`).
|
||||
|
||||
## Funciones del registry — paleta backend Go
|
||||
|
||||
| ID | Para |
|
||||
|---|---|
|
||||
| `sqlite_apply_versioned_migrations_go_infra` | Aplicar `migrations/*.sql` con embed.FS al arrancar |
|
||||
| `sqlite_open_*_go_infra` | Abrir SQLite con WAL + foreign keys + ping |
|
||||
| `http_serve_go_infra` | Listener con graceful shutdown via ctx |
|
||||
| `http_router_go_infra` | `*http.ServeMux` desde `[]Route` declarativo |
|
||||
| `http_json_response_go_infra` | Escribir JSON + status code |
|
||||
| `http_error_response_go_infra` | Errores con shape consistente |
|
||||
| `http_parse_body_go_infra` | Decode JSON con maxBytes (anti-DoS) |
|
||||
| `http_cors_middleware_go_infra` | CORS para frontend ImGui/web |
|
||||
| `http_logger_middleware_go_infra` | Log estructurado por request |
|
||||
| `http_middleware_chain_go_infra` | Componer middlewares |
|
||||
| `http_session_cookie_middleware_go_infra` | Sesiones cookie con TTL |
|
||||
| `http_session_cookie_set_go_infra` / `clear` / `extract` | CRUD cookie sesion |
|
||||
| `jwt_middleware_go_infra` | Auth JWT (alternativa a cookies) |
|
||||
| `crud_generate_handlers_go_infra` | 5 handlers REST a partir de `CRUDResource` |
|
||||
| `crud_register_routes_go_infra` | Registrar `GET/POST/PUT/DELETE /resource[/id]` en mux |
|
||||
| `file_serve_go_infra` | Static files con `Cache-Control: max-age` |
|
||||
| `health_check_http_go_infra` | Polling de URL hasta 2xx (smoke en tests) |
|
||||
| `logger_go_infra` | Logger estructurado (consumido por log_window apps C++) |
|
||||
| `notify_telegram_go_infra` | Alertas a chat (opcional) |
|
||||
| `ssh_exec_go_infra` | Ejecutar comandos en host remoto (services cross-PC) |
|
||||
|
||||
Buscar mas: `mcp__registry__fn_search query="http" lang="go" tag="service"`.
|
||||
|
||||
## Patron CRUD declarativo
|
||||
|
||||
Si el backend expone una entidad CRUD plana (cards, jobs, deploys), evita escribir 5 handlers a mano:
|
||||
|
||||
```go
|
||||
res := infra.CRUDResource{
|
||||
Table: "cards",
|
||||
PKColumn: "id",
|
||||
Columns: []string{"id","title","status","created_at"},
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
infra.CRUDRegisterRoutes(mux, "/api/cards", res, db)
|
||||
// GET /api/cards → list
|
||||
// GET /api/cards/{id} → get
|
||||
// POST /api/cards → create
|
||||
// PUT /api/cards/{id} → update
|
||||
// DELETE /api/cards/{id} → delete
|
||||
```
|
||||
|
||||
## Service: block (issue 0105) — checklist obligatorio
|
||||
|
||||
Toda app con `tag: service` declara:
|
||||
|
||||
- `port` (null si stdio).
|
||||
- `health_endpoint` (ruta GET 2xx → sano).
|
||||
- `health_timeout_s` (default 3).
|
||||
- `systemd_unit` si `runtime` empieza con `systemd-`.
|
||||
- `systemd_scope`: `user` | `system`.
|
||||
- `restart_policy`: **`always`** (no `on-failure` — ver gotcha en `function_tags.md`).
|
||||
- `runtime`: `systemd-user` | `systemd-system` | `docker-compose` | `stdio` | `manual`.
|
||||
- `pc_targets[]`: pc_ids de `pc_locations`.
|
||||
|
||||
Auditar: `fn doctor services-spec`.
|
||||
|
||||
## Gotchas comunes
|
||||
|
||||
| Gotcha | Causa | Solucion |
|
||||
|---|---|---|
|
||||
| Service muere en `SIGTERM` y systemd no reinicia | `Restart=on-failure` en unit | Cambiar a `Restart=always`. `sqlite_api` cayo 20h asi (2026-05-17) |
|
||||
| `database is locked` al escribir desde 2 procesos | SQLite sin WAL | Abrir con `?_journal_mode=WAL&_foreign_keys=on` (usar `sqlite_open_*`) |
|
||||
| Frontend ImGui no recibe CORS preflight | Falta `http_cors_middleware` | Anadir middleware antes del router |
|
||||
| Body POST llega vacio | `json.NewDecoder(r.Body)` sin maxBytes — falla silenciosa | Usar `http_parse_body_go_infra` con `maxBytes=10MB` |
|
||||
| Migracion 002 borra datos en otros PCs al hacer `fn sync` | Migracion destructiva | Solo aditivo. Ver `db_migrations.md` |
|
||||
| MCP tool no aparece en Claude tras anadirla | Schema no regenerado | Rebuild + `claude mcp remove/add` o reset cache |
|
||||
|
||||
## Fronteras (que NO cubre esta capability)
|
||||
|
||||
- **Apps C++ frontend** → `cpp_apps.md` + capability `cpp-dashboard-viz`.
|
||||
- **Deploy** del backend a VPS → capability `deploy` (Docker+Traefik, systemd, rsync).
|
||||
- **Frontend web Vite/React/Mantine** consumido por el backend → capability `mantine`.
|
||||
- **Migraciones de schema** → regla `db_migrations.md`.
|
||||
- **Telemetria de calls** del agente al registry → `registry_calls.md` + `call_monitor` app.
|
||||
|
||||
## Cuando promover a pipeline / extraer al registry
|
||||
|
||||
Si tras construir el N-esimo Go service detectas patron repetido (>2 apps):
|
||||
|
||||
1. Buscar funcion existente en registry (`mcp__registry__fn_search`).
|
||||
2. Si no existe → spawn `fn-constructor` con tag de grupo (`http`, `service`, etc.).
|
||||
3. Migrar las apps existentes para consumirla.
|
||||
4. Capability page se actualiza sola via `fn doctor capabilities`.
|
||||
@@ -62,7 +62,7 @@ unit=$(./fn run systemd_generate_unit \
|
||||
|
||||
## Fronteras
|
||||
|
||||
- **NO maneja timers ni paths units**. Solo service units. Para cron/timer usa Dagu o cron clasico.
|
||||
- **NO maneja timers ni paths units**. Solo service units. Para cron/timer usa dag_engine (`apps/dag_engine/`, `schedule:` en el YAML del DAG) o cron clasico.
|
||||
- **NO genera socket activation**. Asume que la app abre su propio puerto.
|
||||
- **NO instala unit a nivel usuario (`--user`)** salvo que el caller pase la flag al systemctl. Default es `/etc/systemd/system/`.
|
||||
- Apps en `apps/` que se ejecutan local pero NO son service de larga duracion: NO necesitan systemd. Solo apps con `tags: [service]` en su `app.md`.
|
||||
|
||||
+29
-26
@@ -1,6 +1,6 @@
|
||||
# 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 Dagu y operations.db.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -8,7 +8,7 @@ Contrato comun para apps, pipelines y automatizaciones del fn-registry. Define c
|
||||
|
||||
Tenemos dos patrones que ya funcionan:
|
||||
|
||||
- **Dagu**: orquesta DAGs con `command`/`script`, usa exit codes para decidir flujo, tiene handlers globales y scheduling cron.
|
||||
- **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.
|
||||
@@ -73,7 +73,8 @@ 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` | Dagu / apps genericas |
|
||||
| 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 |
|
||||
|
||||
@@ -96,16 +97,16 @@ Toda app/script que siga este estandar DEBE salir con uno de estos tres codigos:
|
||||
- Si algun paso falla pero todos los fallos tenian `continue_on_error: true` → `2`
|
||||
- Un timeout agotado cuenta como fallo del paso
|
||||
|
||||
### Compatibilidad con Dagu
|
||||
### Compatibilidad con dag_engine
|
||||
|
||||
Dagu interpreta exit code != 0 como fallo. Para que `2` (partial) no dispare el handler de failure en Dagu, el DAG que llama a la app puede usar:
|
||||
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`:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: run_app
|
||||
command: ./mi_app --script flujo.yaml
|
||||
continue_on:
|
||||
failure: true # no abortar el DAG por parcial
|
||||
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]
|
||||
@@ -152,10 +153,10 @@ Toda app DEBE escribir un **resumen JSON en stdout** al finalizar (despues de su
|
||||
### Separacion humano / maquina
|
||||
|
||||
- **stderr**: logs legibles para humanos (progreso, warnings)
|
||||
- **stdout**: output JSON estructurado al final (para Dagu, hooks, pipes)
|
||||
- **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 Dagu capture el JSON:
|
||||
Esto permite que dag_engine capture el JSON:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
@@ -197,23 +198,24 @@ hooks:
|
||||
| `{{.StepName}}` | string | Nombre del paso (solo en on_step_error) |
|
||||
| `{{.Error}}` | string | Mensaje de error (solo en on_step_error/on_failure) |
|
||||
|
||||
### En Dagu (handlers globales)
|
||||
### En dag_engine (handlers globales)
|
||||
|
||||
Los hooks del YAML son locales al flujo. Para orquestacion, Dagu maneja sus propios handlers:
|
||||
Los hooks del YAML son locales al flujo. Para orquestacion, dag_engine maneja sus propios handlers (`init/success/failure/exit`, alias `handler_on`):
|
||||
|
||||
```yaml
|
||||
# dags/mi_automatizacion.yaml
|
||||
# apps/dag_engine/dags_migrated/mi_automatizacion.yaml
|
||||
name: mi_automatizacion
|
||||
handlers:
|
||||
failure:
|
||||
- name: alerta
|
||||
command: echo "Fallo en mi_automatizacion" >> ~/dagu/logs/failures.log
|
||||
command: echo "Fallo en mi_automatizacion" >> /var/log/fn_registry/failures.log
|
||||
success:
|
||||
- name: registrar
|
||||
command: echo "OK $(date)" >> ~/dagu/logs/success.log
|
||||
command: echo "OK $(date)" >> /var/log/fn_registry/success.log
|
||||
steps:
|
||||
- name: ejecutar
|
||||
command: ./fn run mi_pipeline --script flujo.yaml
|
||||
function: mi_pipeline_bash_pipelines
|
||||
args: ["flujo.yaml"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -249,15 +251,15 @@ Este patron ya esta implementado en `script_navegador/ops.go` y sirve como refer
|
||||
|
||||
---
|
||||
|
||||
## 6. Flujo completo: App → Dagu → Hooks
|
||||
## 6. Flujo completo: App → dag_engine → Hooks
|
||||
|
||||
```
|
||||
Dagu (scheduler)
|
||||
dag_engine (scheduler)
|
||||
│
|
||||
cron / manual
|
||||
cron / manual / API
|
||||
│
|
||||
┌────▼────┐
|
||||
│ DAG │ dags/mi_dag.yaml
|
||||
│ DAG │ apps/dag_engine/dags_migrated/mi_dag.yaml
|
||||
│ step │ command: ./mi_app --script flujo.yaml --json
|
||||
└────┬────┘
|
||||
│
|
||||
@@ -280,11 +282,11 @@ Este patron ya esta implementado en `script_navegador/ops.go` y sirve como refer
|
||||
success failure partial
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Dagu OK Dagu FAIL Dagu FAIL*
|
||||
handler handler (configurable)
|
||||
engine OK engine FAIL engine FAIL*
|
||||
handler handler (configurable)
|
||||
```
|
||||
|
||||
*Con `continue_on: failure: true` en el step de Dagu, exit 2 no aborta el DAG.
|
||||
*Con `continue_on: { exit_code: [2] }` en el step, exit 2 no aborta el DAG.
|
||||
|
||||
---
|
||||
|
||||
@@ -330,19 +332,20 @@ hooks:
|
||||
on_failure: "echo 'Backup fallo' >> /tmp/backup_errors.log"
|
||||
```
|
||||
|
||||
### Invocacion desde Dagu
|
||||
### Invocacion desde dag_engine
|
||||
|
||||
```yaml
|
||||
name: backup_diario
|
||||
schedule: "0 2 * * *"
|
||||
working_dir: /home/lucas/fn_registry/apps/mi_app
|
||||
steps:
|
||||
- name: backup
|
||||
command: ./fn run backup_db --script flujo.yaml --json
|
||||
working_dir: /home/lucas/fn_registry/apps/mi_app
|
||||
function: backup_db_bash_pipelines
|
||||
args: ["flujo.yaml", "--json"]
|
||||
handlers:
|
||||
failure:
|
||||
- name: alerta
|
||||
command: echo "Backup fallo $(date)" >> ~/dagu/logs/failures.log
|
||||
command: echo "Backup fallo $(date)" >> /var/log/fn_registry/failures.log
|
||||
```
|
||||
|
||||
### Output esperado (stdout)
|
||||
@@ -380,5 +383,5 @@ Exit code: `2` (partial — push fallo pero tenia continue_on_error).
|
||||
| Output humano | stderr (progreso, warnings) |
|
||||
| Error handling | `continue_on_error` por paso, hooks por resultado |
|
||||
| Trazabilidad | operations.db (executions + logs) |
|
||||
| Orquestacion | Dagu como scheduler, apps como ejecutores |
|
||||
| Orquestacion | dag_engine como scheduler (`apps/dag_engine/`), apps/funciones del registry como ejecutores |
|
||||
| Funciones | Reutilizar del registry, actions por dominio |
|
||||
|
||||
Reference in New Issue
Block a user