feat(infra): auto-commit con 56 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:22:55 +02:00
parent c1071a82b3
commit 32c7336bf6
56 changed files with 5307 additions and 100 deletions
@@ -0,0 +1,93 @@
---
name: discover_local_services
kind: function
lang: py
domain: infra
version: "1.1.0"
purity: impure
signature: "discover_local_services(manifest_path: str, include_registry: bool = True) -> list[dict]"
description: "Descubre los servicios locales del sistema local_hub expuestos como subdominios *.localhost. Lee el manifiesto YAML, normaliza la metadata de cada servicio, opcionalmente añade los servicios del registry con puerto via fn doctor, y comprueba up/down por chequeo de puerto TCP en 127.0.0.1. Robusta: no lanza por servicio caido (up=False) ni por fallo de fn doctor."
tags: [local-hub, infra, services, discovery, caddy, glance, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [json, os, socket, subprocess, sys, yaml]
params:
- name: manifest_path
desc: "ruta al manifiesto YAML de servicios (apps/local_hub/local_services.yaml) con claves dashboard_subdomain, glance_port y services[]"
- name: include_registry
desc: "si True, añade los servicios del registry con port>0 que no esten ya en el manifiesto (dedup por port y por subdomain), obtenidos de fn doctor services-spec --json"
output: "lista de dicts normalizados, cada uno con las claves name, subdomain, port, health_path, title, icon, category, rewrite_host (bool, passthrough del manifiesto; False para servicios del registry; lo consume render_caddyfile para reescribir el header Host), up (bool de estado vivo por puerto TCP)"
tested: true
tests:
- "test_golden_service_up_with_all_keys"
- "test_edge_closed_port_is_down"
- "test_defaults_derived_for_missing_fields"
- "test_empty_manifest_returns_empty_list"
- "test_rewrite_host_passthrough_desde_manifiesto"
test_file_path: "python/functions/infra/discover_local_services_test.py"
file_path: "python/functions/infra/discover_local_services.py"
---
## Ejemplo
```python
from discover_local_services import discover_local_services
# Solo manifiesto (sin tocar el registry):
servicios = discover_local_services(
"apps/local_hub/local_services.yaml",
include_registry=False,
)
for s in servicios:
estado = "UP" if s["up"] else "DOWN"
print(f'{s["title"]:<16} {s["subdomain"]}.localhost -> :{s["port"]} [{estado}]')
# Manifiesto + servicios del registry con puerto:
todos = discover_local_services("apps/local_hub/local_services.yaml")
print(len([s for s in todos if s["up"]]), "servicios vivos")
```
Como script (imprime JSON a stdout):
```bash
python/.venv/bin/python3 python/functions/infra/discover_local_services.py apps/local_hub/local_services.yaml
```
## Cuando usarla
Úsala como fase de descubrimiento del sistema `local_hub` antes de renderizar el
Caddyfile o la config de Glance: cuando necesites la lista normalizada de servicios
locales (`*.localhost`) con su estado up/down resuelto. También cuando quieras un
inventario unificado de servicios manuales (contenedores, daemons de terceros) más
los servicios del registry con puerto, deduplicados.
## Gotchas
- `up` se decide por **conexión TCP** a `127.0.0.1:<port>` con timeout 0.5s, NO por
GET HTTP. Un servicio puede aceptar la conexión y devolver 404/500 en `/` y aun
así marcar `up=True`. Es intencional: solo valida que el puerto esté escuchando.
- Solo comprueba `127.0.0.1` (loopback). Servicios que bindean únicamente a otra
interfaz se reportan como `down`.
- `include_registry=True` ejecuta `fn doctor services-spec --json` (fallback a
`services --json`) como subproceso desde la raíz del repo. Si `fn` no está, falla,
tarda más de 20s o devuelve JSON inválido, la función **no lanza**: sigue solo con
el manifiesto. Por eso el resultado puede variar según el entorno.
- La raíz del repo se resuelve por `FN_REGISTRY_ROOT` o subiendo directorios hasta
encontrar `registry.db`. Si no la encuentra, usa el cwd.
- El dedup del registry es por `port` Y por `subdomain`: un servicio del registry
cuyo puerto o subdominio derivado ya esté en el manifiesto se omite.
- El subdominio de un servicio del registry se deriva por una tabla de alias
(`dag_engine`->`dag`, `registry_api`->`registry`, `sqlite_api`->`sqlite`,
`osint_db`->`osint`, ...) y, para el resto, el primer token antes de `_`.
- Lanza `RuntimeError` solo si el manifiesto no se puede leer o parsear (path
inexistente, YAML inválido). Eso sí es un error duro.
- La clave `rewrite_host` es passthrough del manifiesto (default `False`); para
los servicios añadidos desde el registry siempre es `False`. La consume
`render_caddyfile` para emitir `header_up Host` en el bloque del servicio.
## Capability growth log
- v1.1.0 (2026-06-20) — añade clave rewrite_host (passthrough del manifiesto) para que render_caddyfile reescriba el Host