--- name: pandora-senales description: Construir o actualizar un generador de señales (emisor) para Pandora Cloud — el proceso, script o agente que publica estado en `/api/v1/signals`. Úsala cuando haya que crear un emisor nuevo, adaptar uno existente a la spec `pandora.signal/v1`, programar su cadencia (systemd timer o launchd), o revisar por qué un emisor no publica. Incluye el estándar de dónde va cada fichero, el contrato de la muestra, el catálogo de vistas y las trampas conocidas. --- # Generadores de señales para Pandora Cloud Un **emisor** es un proceso externo que publica su estado en Pandora Cloud con `PUT /api/v1/signals/{emisor}/{señal}`. Esta skill fija **el estándar** para construirlos, para que cualquiera (persona o agente) encuentre el código y sepa cómo se hace. ## Regla de oro: dónde va Todo emisor vive **en el repo del producto que emite**, nunca suelto en una máquina: ``` deploy// ├── README.md # qué mide, qué publica, cómo se instala y se desinstala ├── .py # el publicador (un solo ejecutable) ├── .service # systemd --user (Linux) ├── .timer # la cadencia (Linux) ├── com..plist # LaunchAgent (macOS), si aplica └── install.sh # instalación idempotente en remoto ``` - **La configuración no va al repo**: `~/.config/pandora-cloud/.env` (`0600`) con la URL de la API y el token. `install.sh` la **conserva** si ya existe. - Antes de crear un emisor, **busca si ya existe** (`ls deploy/`, `grep -r`). Duplicar emisores es la causa número uno de señales fantasma. ## La cadencia se declara dos veces (a propósito) 1. En el **sistema**: `systemd --user` con `.timer` (Linux) o LaunchAgent (macOS). 2. En la **señal**: `mode=periodic` + `interval` + `keepalive`. Si solo estuviera el timer, un emisor muerto o una máquina apagada serían **invisibles**. Con `keepalive`, Pandora Cloud pasa la señal a `dead` cuando deja de llegar. ## Contrato de la muestra ```json PUT /api/v1/signals/{emisor}/{señal} { "spec": "pandora.signal/v1", "signal": { "name": "Uso del equipo — tuk", "mode": "periodic", "interval": 300, "keepalive": 900, "shape": "static", "agent_id": 13, "tags": ["uso"] }, "sample": { "seq": 42, "ts": "2026-09-27T10:00:00Z", "status": "ok", "lifecycle": "done", "summary": "una línea" }, "data": { }, "render": { "layout": "grid", "panels": [ ] }, "links": [ { "label": "Panel", "href": "https://…" } ] } ``` | Campo | Nota | | --- | --- | | `{emisor}` | **Ancla la señal al agente por su `agent_name` (Endpoint Name)**, inmutable (endpoint: del XML; cloud: el primer nombre que se le dio): úsalo tal cual (cuelga de su ficha aunque le cambies el nombre). Si no hay agente, el nombre corto de la máquina. **No** lo inventes | | `{señal}` | Estable por tarea; admite `/` (`seo/site-a/informe`) | | `sample.status` | **Criticidad**: `ok`, `warning`, `critical`, `unknown` | | `sample.lifecycle` | **Ciclo de vida**: `running`, `waiting`, `done`, `error`, `cancelled` | | `sample.seq` | Entero creciente: permite detectar huecos y duplicados | | `signal.agent_id` | Opcional. Id **interno** (`hosts.id`). Si falta, se busca un endpoint por el **`agent_name` (Endpoint Name)** del emisor; si no hay, se crea uno nuevo con ese nombre | | `shape` | `static` \| `dynamic` \| `fluid`: qué se puede comparar con la muestra anterior | ### No hay histórico La señal guarda **la última muestra**. Para tendencia: `POST /api/v1/metrics` y **referénciala** desde el panel (`"options":{"metric":"…"}`). Nunca mandes la serie dentro de la muestra salvo caso puntual de `event`. ## Catálogo de vistas (`render.panels[]`) `kv_table`, `table`, `status`, `log`, `text`, `stat`, `gauge`, `timeseries`, `links`. Cada panel apunta a `data` con **JSON Pointer** (`source`), o referencia una serie (`options.metric` / `options.check_id`). Una vista desconocida o una ruta sin datos **se pintan como tabla con aviso**: nunca rompen la página. `text` admite **markdown restringido** (negritas, cursivas, saltos). Nada de HTML. ## Trampas que ya nos han mordido 1. **La API rechaza campos desconocidos** (`DisallowUnknownFields`). El cuerpo tiene que ser exacto: mandar `name`, `message` o `lease_seconds` de la versión anterior da `bad_json`. 2. **`mode` por defecto es `periodic`**: si es un aviso puntual, declara `event` explícitamente. 3. **Sin `keepalive` no hay detección de caída** (solo `stale`, informativo). 4. **No inventes el `origin`**: lo que no coincide con la máquina confunde a quien mantiene el emisor después. 5. **La muestra reemplaza a la anterior**: si el publicador falla a medias, deja la señal en un estado raro. Publica siempre el cuerpo completo. 6. **`install.sh` debe ser idempotente** y conservar la config del equipo. ## Lista de comprobación - [ ] Existe ya un emisor que cubra esto (si no, ¿por qué no reutilizarlo?) - [ ] `deploy//` con script, unidades, `install.sh` y `README.md` - [ ] La config vive en `~/.config/pandora-cloud/.env` (`0600`), no en el repo - [ ] `mode`/`interval`/`keepalive` coherentes con el timer - [ ] `status` (criticidad) y `lifecycle` (ciclo) son cosas distintas y las dos están bien - [ ] `seq` incrementado en cada envío - [ ] Si hay tendencia: métrica publicada **y** referenciada en el panel - [ ] Probado en el equipo real: la señal aparece y **se actualiza** (no solo la primera vez) - [ ] `README.md` dice cómo desinstalarlo ## Referencia Documentación de la API (visible desde el portal): sección **«Generadores de señales»**. Implementación de referencia: `deploy/aw-usage-cloud/`.