@kwirthmagnify/kwirth-docs-pinocchio 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ # Recorrido por la UI
2
+
3
+ Pinocchio es un canal, así que se abre como cualquier otro canal de kwirth: eliges el canal **Pinocchio** en
4
+ el selector y lo arrancas. No tiene diálogo de *Setup* — no hay nada que configurar antes de arrancar.
5
+
6
+ ## La pestaña
7
+
8
+ La pestaña es una única tarjeta con una cabecera y un flujo de mensajes que crece hacia abajo.
9
+
10
+ ![La pestaña de Pinocchio recién arrancada, todavía sin análisis](../images/ui-tab.png)
11
+
12
+ **Cabecera**
13
+
14
+ | Elemento | Qué es |
15
+ |--------------|-------------------------------------------------------------------------------|
16
+ | `Events: N` | Número de entradas en el flujo (análisis + mensajes). No es el número de findings. |
17
+ | `Status` | `started`, `paused` o `stopped`, según el estado del canal. |
18
+ | **Clear** | Abre el diálogo de limpieza (ver abajo). |
19
+ | **Playground** | Abre el banco de pruebas. Ver [El Playground](06-playground.md). |
20
+ | **Config** | Abre el menú de configuración. |
21
+
22
+ **Flujo**
23
+
24
+ El cuerpo hace autoscroll al final mientras estés abajo del todo; si subes a leer algo, deja de seguirte
25
+ hasta que vuelvas al final. Cada análisis se pinta como:
26
+
27
+ 1. Una línea de cabecera con la marca de tiempo, el evento (`ADDED Deployment 'nginx' in namespace 'demo'`),
28
+ el LLM usado y los tokens consumidos, más un botón **Report**.
29
+ 2. Los **findings**, ordenados de `critical` a `low`, cada uno con su etiqueta de severidad.
30
+ 3. Una **tira de resumen** con PSS, el recuento por severidad y el riesgo global.
31
+
32
+ Los findings y la tira de resumen son **clicables** y abren su diálogo de detalle.
33
+
34
+ ## El menú Config
35
+
36
+ El botón **Config** abre un menú de tres entradas, la primera de ellas un grupo desplegable. El orden no es
37
+ casual: cada una depende de la anterior.
38
+
39
+ ![El menú Config, con el grupo AI desplegado](../images/ui-config-menu.png)
40
+
41
+ | Entrada | Se habilita cuando… | Qué configura |
42
+ |-------------------|----------------------------------------|--------------------------------------------------|
43
+ | **AI** ▸ **AI providers** | siempre | Proveedores de IA (OpenAI, Google, …) y su API key |
44
+ | **AI** ▸ **AI models** | hay al menos un provider | Modelos concretos, con temperatura y coste |
45
+ | **Trigger** | hay al menos un LLM | Los triggers y sus versiones |
46
+ | **Import / Export** | siempre | Volcado y carga de triggers en JSON |
47
+
48
+ **AI** es un grupo plegable —el patrón habitual de kwirth, el mismo que usan Excubitor y Agora— y **arranca
49
+ plegado**: hay que clicarlo para ver *AI providers* y *AI models*. Es lo que menos se toca una vez montado,
50
+ por eso está recogido.
51
+
52
+ Si abres Pinocchio por primera vez y **Trigger** está en gris, no es un fallo: es que aún no hay un LLM
53
+ configurado. Empieza por **AI ▸ AI providers**. El detalle está en
54
+ [Providers y modelos de IA](../admin/02-ai-config.md).
55
+
56
+ > Los diálogos de **AI providers** y **AI models** no son de Pinocchio: son los diálogos comunes de IA de
57
+ > kwirth (`AiConfigProvider` / `AiConfigLlm`), los mismos que usan los demás plugins con IA. Lo que configures
58
+ > aquí lo verán también ellos.
59
+
60
+ ## Los diálogos de detalle
61
+
62
+ **Detalle de un finding** (clic en cualquier finding) — muestra, en dos columnas, todo lo que el modelo
63
+ rellenó: Control ID, categoría, confianza, *risk score*, descripción, evidencia e impacto a la izquierda;
64
+ remediación y referencias a la derecha. Los campos vacíos no se pintan.
65
+
66
+ **Detalle del análisis** (clic en la tira de resumen) — el recurso analizado (kind, nombre, namespace,
67
+ imágenes), PSS actual y objetivo, riesgo global y recuento por severidad a la izquierda; controles superados,
68
+ lo que el modelo **no pudo ver** y los próximos pasos a la derecha.
69
+
70
+ **Report** (botón *Report* de la cabecera del análisis) — abre el informe en markdown renderizado. El botón
71
+ está deshabilitado si ese análisis no trajo informe.
72
+
73
+ ## El diálogo Clear
74
+
75
+ Hay dos limpiezas distintas y conviene no confundirlas, porque una es local y la otra afecta a todo el mundo:
76
+
77
+ ![El diálogo Clear findings](../images/ui-clear-dialog.png)
78
+
79
+ - **Clear my view** — vacía **tu** pantalla. El backend conserva sus análisis y te los reenviará si
80
+ reconectas.
81
+ - **Clear back** — borra los análisis guardados **en el canal**. Afecta a todos los fronts conectados y no
82
+ tiene vuelta atrás.
83
+
84
+ ## Colores de severidad
85
+
86
+ Los mismos en toda la UI:
87
+
88
+ | Nivel | Color |
89
+ |------------|----------|
90
+ | `critical` | rojo |
91
+ | `high` | naranja |
92
+ | `medium` | verde |
93
+ | `low` | gris |
94
+
95
+ > ⚠️ Sí: `medium` se pinta en **verde**. Es el comportamiento actual del plugin y sorprende la primera vez.
96
+ > Guíate por el texto de la etiqueta, no por el color.
@@ -0,0 +1,160 @@
1
+ # Triggers, versiones y prompts
2
+
3
+ Toda la configuración de Pinocchio se reduce a una lista de **triggers**. Esta página explica el modelo de
4
+ datos y, sobre todo, **qué hace realmente el backend con cada campo**, que no siempre es lo que el nombre
5
+ sugiere.
6
+
7
+ ## Trigger
8
+
9
+ Un trigger define **cuándo** se dispara el análisis:
10
+
11
+ | Campo | Aplica a | Qué hace |
12
+ |-------------|-------------|--------------------------------------------------------------------------------|
13
+ | `id` | ambos | Identificador libre. Es lo que ves en la lista y lo que se usa al importar/exportar. |
14
+ | `trigger` | ambos | `artifact` (objeto de Kubernetes) o `business` (JSON externo). |
15
+ | `kind` | `artifact` | El kind de Kubernetes a vigilar. Debe coincidir **exactamente** con `obj.kind`. |
16
+ | `k8sEvent` | `artifact` | `ADDED`, `MODIFIED`, `DELETED` — o vacío (*Any*) para no filtrar por evento. |
17
+ | `versions` | ambos | Lista de versiones. Ver abajo. |
18
+
19
+ El matching de un evento de Kubernetes es literal:
20
+
21
+ ```
22
+ trigger.trigger === 'artifact'
23
+ && trigger.kind === evento.obj.kind
24
+ && (!trigger.k8sEvent || trigger.k8sEvent === evento.type)
25
+ ```
26
+
27
+ Los triggers de tipo `business` **no filtran por espacio ni por tipo en el matching**: cualquier evento de
28
+ negocio que llegue al canal dispara **todos** los triggers `business` que tengan una versión activa. El campo
29
+ *Spaces* de la versión no se usa para filtrar (ver más abajo).
30
+
31
+ ## Versión
32
+
33
+ Una versión es **el contenido del análisis**: qué se le pregunta al modelo y con qué medios.
34
+
35
+ | Campo | Qué hace |
36
+ |---------------|---------------------------------------------------------------------------------------------|
37
+ | `id` | Identificador dentro del trigger. Único por trigger. |
38
+ | `description` | Texto libre; se ve bajo el id en la lista de versiones. |
39
+ | `enabled` | Si está activa. **Sólo una versión por trigger puede estar activa a la vez.** |
40
+ | `llm` | El id del LLM configurado que se va a usar. |
41
+ | `system` | El *system prompt*. ⚠️ **Se ignora en los triggers `business`** (ver abajo). |
42
+ | `promptType` | `jinja` o `artifact`. Determina cómo se construye el prompt. |
43
+ | `prompt` | La plantilla del prompt. Deshabilitada cuando `promptType` es `artifact`. |
44
+ | `steps` | Máximo de pasos del agente. Si es 0 o vacío, el backend usa **15**. |
45
+ | `tools` | Lista de tools que el modelo puede llamar. |
46
+ | `autoTools` | Si está marcado, se le entrega **todo el catálogo** de tools. |
47
+ | `spaces` | Lista `space.type`. Hoy **no filtra nada**; ver [Límites](../admin/06-limits.md). |
48
+ | `action` | `inform` / `cancel` / `repair`. **Hoy el backend no lo lee**: todo se comporta como `inform`. |
49
+
50
+ ### Por qué "versiones" y no simplemente "triggers"
51
+
52
+ Porque afinar un prompt es un proceso iterativo y **quieres conservar el que funcionaba**. El patrón previsto
53
+ es: clonas la versión activa, retocas el prompt en la copia, la activas (lo que desactiva la anterior
54
+ automáticamente) y comparas resultados. Si empeora, reactivas la vieja con un clic del interruptor.
55
+
56
+ Al clonar una versión, la copia nace **desactivada** a propósito, para que no te cambie el comportamiento sin
57
+ querer.
58
+
59
+ ## Cómo se construye el prompt
60
+
61
+ Este es el punto donde más gente se equivoca. Depende de la combinación tipo de trigger × `promptType`:
62
+
63
+ ### `artifact` + `promptType: jinja`
64
+
65
+ El `prompt` se renderiza con **nunjucks** usando el objeto de Kubernetes como contexto:
66
+
67
+ ```
68
+ prompt = nunjucks.renderString(version.prompt, evento.obj)
69
+ ```
70
+
71
+ Es decir, las variables disponibles son los campos del propio objeto:
72
+
73
+ ```jinja
74
+ Audita este {{ kind }} llamado {{ metadata.name }} del namespace {{ metadata.namespace }}.
75
+
76
+ Imágenes:
77
+ {% for c in spec.template.spec.containers %}- {{ c.image }}
78
+ {% endfor %}
79
+ ```
80
+
81
+ El `system` **sí se usa**. Si está vacío, el backend cae a `'You are a very polite AI system'`.
82
+
83
+ > ⚠️ **El objeto es el contexto de la plantilla, no un adjunto.** El modelo recibe **únicamente el texto
84
+ > renderizado**: lo que tu plantilla no interpole, el modelo no lo ve. Un prompt como
85
+ > `Audita el Deployment {{ metadata.name }} del namespace {{ metadata.namespace }}.` le llega al modelo como
86
+ > `Audita el Deployment api-gateway del namespace demo.` — sin manifiesto, sin imágenes, sin
87
+ > `securityContext`. Y el modelo contesta lo único que puede contestar:
88
+ >
89
+ > ![El modelo pide el manifiesto que la plantilla no le pasó](../images/playground-out.png)
90
+ >
91
+ > Si quieres que audite el recurso, **mete el recurso en la plantilla** (`{{ spec | dump(2) }}`) o usa
92
+ > `promptType: artifact`, que le pasa el objeto entero.
93
+
94
+ ### `artifact` + `promptType: artifact`
95
+
96
+ El prompt es, literalmente, **el objeto entero serializado**:
97
+
98
+ ```
99
+ prompt = JSON.stringify(evento.obj)
100
+ ```
101
+
102
+ El campo `prompt` se ignora por completo (y la UI lo deshabilita). Toda la instrucción tiene que estar en el
103
+ `system`. Es la opción cómoda cuando el system ya dice "eres un auditor de PSS, analiza el manifiesto que te
104
+ paso".
105
+
106
+ ### `business` (cualquier `promptType`)
107
+
108
+ ```
109
+ prompt = nunjucks.renderString(version.prompt, {})
110
+ ```
111
+
112
+ ⚠️ Dos cosas importantes, ambas comprobables en el código del backend:
113
+
114
+ 1. **El contexto de renderizado está vacío.** Aunque pongas espacios en el campo *Spaces*, los datos del
115
+ evento **no se pasan a la plantilla**. Cualquier `{{ variable }}` que escribas se renderiza como cadena
116
+ vacía. En la práctica, un prompt `business` es texto fijo.
117
+ 2. **El `system` de la versión se ignora.** El backend usa un system fijo:
118
+ *"Use the tools provided to find information, and once you have the data, format your final response
119
+ strictly as a JSON object according to the schema."*
120
+
121
+ Por eso un trigger `business` sólo tiene sentido si su valor está en las **tools**: el prompt fijo plantea la
122
+ pregunta ("¿está el clúster saturado?") y el modelo la resuelve consultando el clúster.
123
+
124
+ Ambos comportamientos están recogidos en [Límites conocidos](../admin/06-limits.md).
125
+
126
+ ## Qué devuelve cada tipo
127
+
128
+ | Tipo | Esquema de salida |
129
+ |------------|--------------------------------------------------------------------------------------------------------|
130
+ | `artifact` | Objeto completo: `resource`, `pss_current`, `pss_target`, `score_summary`, `global_risk`, `controls_passed`, `not_visible`, `next_steps`, `report`, `findings[]`, `hardened_yaml`. |
131
+ | `business` | Sólo `{ response: string }`, que se muestra como un mensaje de texto en la pestaña. |
132
+
133
+ El detalle de cada campo está en [Leer los findings](05-findings.md).
134
+
135
+ ## Un trigger de ejemplo, entero
136
+
137
+ ```json
138
+ {
139
+ "id": "pss-deployments",
140
+ "trigger": "artifact",
141
+ "kind": "Deployment",
142
+ "k8sEvent": "ADDED",
143
+ "versions": [
144
+ {
145
+ "id": "v2",
146
+ "description": "PSS restricted + contexto de eventos",
147
+ "enabled": true,
148
+ "llm": "gemini-flash",
149
+ "promptType": "jinja",
150
+ "system": "Eres un auditor de seguridad de Kubernetes. Evalúa contra los Pod Security Standards y devuelve findings accionables.",
151
+ "prompt": "Audita el Deployment {{ metadata.name }} en {{ metadata.namespace }}.\n\nManifiesto:\n{{ spec | dump(2) }}",
152
+ "action": "inform",
153
+ "steps": 8,
154
+ "tools": ["get_deployment_yaml", "get_object_events", "get_workload_config_refs"],
155
+ "autoTools": false,
156
+ "spaces": []
157
+ }
158
+ ]
159
+ }
160
+ ```
@@ -0,0 +1,96 @@
1
+ # Configurar triggers
2
+
3
+ **Config → Trigger** abre el editor. Es un diálogo a dos paneles: a la izquierda la lista de triggers y, bajo
4
+ ella, las versiones del trigger seleccionado; a la derecha el editor de la versión.
5
+
6
+ ![El editor de triggers, con un trigger `artifact` sobre Deployment/ADDED](../images/triggers-dialog.png)
7
+
8
+ ## Panel izquierdo
9
+
10
+ **Triggers.** Cada fila muestra el id y, debajo, el tipo y el kind (`artifact · Deployment`). Al final de la
11
+ fila hay dos iconos que aparecen al pasar el ratón: **clonar** y **borrar**.
12
+
13
+ Para crear uno, escribe un id en la caja de abajo y pulsa **+** (o Enter). Si la dejas vacía, se genera
14
+ `trigger-N`. El trigger nace como `artifact` sin kind y sin versiones.
15
+
16
+ Clonar un trigger copia **todo**, versiones incluidas, con el id sufijado `-copy`. Borrar pide confirmación.
17
+
18
+ **Versions.** Debajo, las versiones del trigger seleccionado. Cada una tiene un **interruptor**:
19
+
20
+ > ⚠️ Los interruptores son **excluyentes**. Al activar una versión, el resto se desactivan solas. Es
21
+ > intencionado: un trigger sólo puede tener un comportamiento activo.
22
+
23
+ Al clonar una versión, la copia nace **desactivada**, para que clonar nunca cambie lo que está pasando en
24
+ producción.
25
+
26
+ ## Panel derecho: el editor de la versión
27
+
28
+ Los campos de la fila superior se dividen en dos grupos y **no se guardan igual**:
29
+
30
+ **Campos del trigger** — `Trigger ID`, `Trigger type`, `Kind`, `K8s Event`. Se aplican **según los tocas**,
31
+ sin necesidad de pulsar nada más.
32
+
33
+ **Campos de la versión** — todo lo demás. Sólo se guardan al pulsar **Add** / **Update**.
34
+
35
+ | Campo | Notas |
36
+ |---------------|----------------------------------------------------------------------------------------------|
37
+ | `Kind` | Sólo si el tipo es `artifact`. Lista cerrada con los kinds que escucha el canal. |
38
+ | `K8s Event` | Sólo si el tipo es `artifact`. *Any* deja pasar `ADDED`, `MODIFIED` y `DELETED`. |
39
+ | `Spaces` | Sólo si el tipo es `business`. Formato `space.type,space.type`. Hoy no filtra nada. |
40
+ | `Version ID` | Obligatorio: el botón de guardar está deshabilitado sin él. No puede repetirse en el trigger. |
41
+ | `Description` | Texto libre que aparece bajo el id en la lista. |
42
+ | `Action` | `inform` / `cancel` / `repair`. Hoy sólo se comporta como `inform`. |
43
+ | `LLM` | Uno de los LLMs configurados. Sin LLMs no llegas ni a abrir este diálogo. |
44
+ | `Steps` | Máximo de pasos del agente. **0 o vacío ⇒ el backend usa 15**, no 0. |
45
+ | Tool selector | Las tools disponibles, con la casilla *auto*. Ver [Tools y pasos](../admin/03-tools.md). |
46
+ | `System` | El system prompt. Ignorado en triggers `business`. |
47
+ | `Prompt` | La plantilla. Deshabilitada cuando el tipo de prompt es `artifact`. |
48
+
49
+ ## Botones
50
+
51
+ | Botón | Qué hace |
52
+ |------------|----------------------------------------------------------------------------|
53
+ | **New** | Vacía el editor para escribir una versión nueva. |
54
+ | **Clone** | Duplica la versión seleccionada, desactivada, con el id sufijado `-copy`. |
55
+ | **Remove** | Borra la versión seleccionada, con confirmación. |
56
+ | **Add** / **Update** | Guarda. Dice *Add* si estás creando y *Update* si estás editando. |
57
+ | **OK** | Cierra el diálogo y **envía la configuración al backend**. |
58
+ | **Cancel** | Descarta **todo** lo hecho en el diálogo. |
59
+
60
+ > ⚠️ **Add/Update no persiste nada por sí solo.** Trabajas sobre una copia local de la configuración; hasta
61
+ > que no pulses **OK**, el backend no se entera. Si haces cinco cambios y cierras con **Cancel**, pierdes los
62
+ > cinco.
63
+
64
+ ## Receta: auditar cada Deployment nuevo
65
+
66
+ 1. **Config → Trigger**, escribe `pss-deployments` en la caja de la izquierda y pulsa **+**.
67
+ 2. Con el trigger seleccionado: `Trigger type` = `artifact`, `Kind` = `Deployment`, `K8s Event` = `ADDED`.
68
+ 3. `Version ID` = `v1`. `LLM` = el que tengas. `Steps` = 5.
69
+ 4. Tools: marca `get_deployment_yaml` y `get_object_events`.
70
+ 5. `System`:
71
+ ```
72
+ Eres un auditor de seguridad de Kubernetes. Evalúa el recurso contra los Pod Security
73
+ Standards (baseline y restricted). Devuelve findings accionables, con evidencia concreta
74
+ tomada del manifiesto. No inventes controles que no puedas justificar.
75
+ ```
76
+ 6. Tipo de prompt `jinja`, `Prompt`:
77
+ ```jinja
78
+ Audita el Deployment {{ metadata.name }} del namespace {{ metadata.namespace }}.
79
+
80
+ Manifiesto:
81
+ {{ spec | dump(2) }}
82
+ ```
83
+ ⚠️ Las dos últimas líneas no son decorativas: **son las que le pasan el recurso al modelo**. Sin ellas
84
+ el modelo sólo recibe el nombre y el namespace, y te contestará pidiéndote el manifiesto. Ver
85
+ [Triggers, versiones y prompts](03-concepts.md).
86
+ 7. **Add** → activa el interruptor de `v1` → **OK**.
87
+ 8. Despliega algo y míralo aparecer en la pestaña.
88
+
89
+ > Antes de activar un trigger sobre un kind con mucho movimiento (`Pod` + `MODIFIED` es el caso extremo),
90
+ > pásate por [Tools y pasos del agente](../admin/03-tools.md): ahí está la cuenta de lo que eso cuesta.
91
+
92
+ ## Cómo probar sin activar nada
93
+
94
+ No hace falta desplegar recursos de mentira para afinar un prompt. El
95
+ [Playground](06-playground.md) ejecuta exactamente la misma tubería contra un payload que pegas a mano, y
96
+ cuando el resultado te convence lo exporta a un trigger real.
@@ -0,0 +1,108 @@
1
+ # Leer los findings
2
+
3
+ Un análisis de tipo `artifact` no devuelve texto libre: devuelve un **objeto con un esquema fijo** que el
4
+ modelo está obligado a rellenar. Esta página explica qué significa cada campo y —más importante— **cuánto
5
+ te puedes fiar de él**.
6
+
7
+ ## La línea de cabecera
8
+
9
+ ```
10
+ 2026-09-08T10:14:22.145Z ADDED Deployment 'api-gateway' in namespace 'prod'
11
+ [LLM:google/gemini-2.0-flash, IN:4821, OUT:1103]
12
+ ```
13
+
14
+ - El evento que disparó el análisis.
15
+ - El **provider y el modelo** que lo produjeron. Útil cuando comparas versiones de un trigger.
16
+ - **IN / OUT** — tokens de entrada y de salida. Es tu factura. Si un trigger te sorprende en el coste, aquí
17
+ está la respuesta.
18
+
19
+ ## Los findings
20
+
21
+ Cada finding es un hallazgo concreto. En la lista sólo se ven la severidad y la descripción; el resto está a
22
+ un clic.
23
+
24
+ | Campo | Valores | Qué es |
25
+ |----------------|-----------------------------------------------------------------------------------------|--------|
26
+ | `level` | `critical` · `high` · `medium` · `low` | Severidad. Ordena la lista. |
27
+ | `control_id` | texto | Identificador del control que se incumple. |
28
+ | `control_name` | texto | Nombre legible del control. Es el título del diálogo de detalle. |
29
+ | `category` | `privileges` · `identity` · `network` · `filesystem` · `supply_chain` · `resources` · `secrets` · `general` · `platform` | Familia del hallazgo. |
30
+ | `confidence` | `low` · `medium` · `high` | Cuánta seguridad dice tener **el modelo**. |
31
+ | `evidence` | texto | El fragmento concreto que lo demuestra. |
32
+ | `impact` | texto | Qué puede pasar si no se arregla. |
33
+ | `remediation` | texto | Cómo arreglarlo. |
34
+ | `references` | lista | Enlaces o identificadores de referencia. |
35
+ | `risk_score` | número | Puntuación numérica que asigna el modelo. |
36
+
37
+ > **El campo que hay que mirar siempre es `evidence`.** Un finding con evidencia concreta ("el contenedor
38
+ > `api` declara `securityContext.privileged: true`") es verificable en dos segundos. Un finding sin
39
+ > evidencia, o con evidencia genérica, es una alucinación candidata. Cruza `confidence` con `evidence`
40
+ > antes de abrir un ticket.
41
+
42
+ En la lista, si la descripción contiene un fragmento entre ` **` y `** `, se pinta en negrita y subrayado.
43
+ Es un pequeño formateo heredado; no es markdown completo.
44
+
45
+ ## La tira de resumen
46
+
47
+ Bajo los findings aparece una tira con el resumen del análisis. Se puede clicar para ver el detalle completo.
48
+
49
+ **PSS: `baseline` → `restricted`**
50
+
51
+ Los [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) de
52
+ Kubernetes. `pss_current` es el nivel que el modelo considera que cumple el recurso **tal como está**;
53
+ `pss_target` es al que podría llegar aplicando las remediaciones. Los valores posibles son `privileged`,
54
+ `baseline`, `restricted` y `undefined`.
55
+
56
+ `privileged` es el nivel **más permisivo** (sin restricciones) y `restricted` el **más estricto**. Si ves
57
+ `pss_current: privileged`, es una mala noticia, no una buena.
58
+
59
+ **critical:0 high:2 medium:5 low:1**
60
+
61
+ El `score_summary`: recuento de findings por severidad, tal como lo cuenta el modelo.
62
+
63
+ **Riesgo global**
64
+
65
+ `global_risk`, la valoración de conjunto: `low`, `medium`, `high` o `critical`.
66
+
67
+ ## El detalle del análisis
68
+
69
+ Al clicar la tira se abren tres cosas que no caben en la línea de resumen:
70
+
71
+ - **Controls passed** — los controles que el recurso **sí** cumple. Sirve para ver que el modelo ha hecho el
72
+ repaso completo y no sólo ha buscado fallos.
73
+ - **Not visible** — los controles que el modelo **no ha podido evaluar** con la información que tenía. Este
74
+ campo es oro: te dice dónde el análisis es incompleto y qué tool deberías haberle dado. Si `not_visible`
75
+ está lleno, sube los `steps` o añade tools.
76
+ - **Next steps** — la lista de acciones recomendadas, en orden.
77
+
78
+ También muestra el **recurso** analizado (kind, nombre, namespace e imágenes) tal como lo identificó el
79
+ modelo. Compáralo con la línea de cabecera: si no coinciden, el modelo se ha confundido de objeto y el
80
+ análisis entero es sospechoso.
81
+
82
+ ## El informe
83
+
84
+ El botón **Report** abre el campo `report`: un informe **en markdown** redactado por el modelo, pensado para
85
+ leerse como documento y no como lista. Se renderiza con el visor de markdown de kwirth.
86
+
87
+ Es la salida que puedes pegar en un ticket o en un correo. Los findings son para triar; el informe es para
88
+ explicar.
89
+
90
+ ## Cuando algo falla
91
+
92
+ Si la llamada al modelo revienta, Pinocchio **no se calla**: publica un análisis con dos findings de nivel
93
+ `critical`, uno con el mensaje de error legible y otro con el error serializado entero. Verás algo como:
94
+
95
+ ```
96
+ critical Pinocchio analysis ended in error while processing 'events' when
97
+ analyzing 'api-gateway' in namespace 'prod' [Kind:Deployment]
98
+ critical {"name":"AI_APICallError","message":"...
99
+ ```
100
+
101
+ Las causas más habituales: API key caducada, cuota agotada en el proveedor, o el modelo elegido no soporta
102
+ salida estructurada. El segundo finding, aunque sea feo, suele traer el mensaje exacto del proveedor.
103
+
104
+ ## Análisis de triggers `business`
105
+
106
+ Los triggers de negocio **no producen findings**. Su salida es un único campo de texto que aparece en el
107
+ flujo como un mensaje normal, precedido de otro mensaje con el evento recibido. Nada de PSS, ni severidades,
108
+ ni informe.
@@ -0,0 +1,134 @@
1
+ # El Playground
2
+
3
+ El Playground es el banco de pruebas: te deja **ejecutar la tubería completa** —prompt, modelo, tools— contra
4
+ un payload que pegas a mano, sin crear ningún trigger y sin tocar el clúster. Es donde se afinan los prompts.
5
+
6
+ Se abre con el botón **Playground** de la cabecera de la pestaña.
7
+
8
+ ## El ciclo de trabajo
9
+
10
+ ```
11
+ Pestaña LLM Pestaña Call Pestaña IN / OUT
12
+ ----------- ------------ ----------------
13
+ elige modelo --> escribe system --> Apply Config --> Fire --> mira la traza
14
+ pega el payload y prompt, tools (sube al back) (inyecta
15
+ el evento)
16
+ ```
17
+
18
+ Dos botones, en este orden obligatorio:
19
+
20
+ 1. **Apply Config** — sube al backend el modelo, los pasos, las tools, el system y el prompt. Mientras no lo
21
+ pulses, **Fire** está deshabilitado. El botón se pone verde y dice *Config applied*.
22
+ 2. **Fire** — inyecta el evento. Cualquier cambio en el modelo, los pasos, las tools, el system o el prompt
23
+ **vuelve a marcar la config como sucia** y tendrás que aplicarla otra vez.
24
+
25
+ ## Pestaña LLM — el evento de entrada
26
+
27
+ | Campo | Qué es |
28
+ |---------------|-----------------------------------------------------------------------|
29
+ | `LLM` | Uno de los LLMs configurados. |
30
+ | `Max steps` | Tope de pasos del agente para esta prueba. |
31
+ | Business / Artifact | El tipo de evento que vas a simular. |
32
+ | `Artifact Kind` | Sólo en modo Artifact. Se inyecta como `kind` si el payload no lo trae. |
33
+ | `K8s Event` | Sólo en modo Artifact. ⚠️ Ver la advertencia de más abajo. |
34
+ | `Space` / `Type` | Sólo en modo Business. ⚠️ Ver la advertencia de más abajo. |
35
+ | El textarea grande | El payload: el JSON del artefacto, o el evento de negocio. |
36
+
37
+ ![La pestaña LLM del Playground, con un Deployment de ejemplo](../images/playground-llm.png)
38
+
39
+ El iconito de reloj junto a las cajas abre el **historial**: los últimos 25 valores que has usado en ese
40
+ campo, con una papelera para borrar los que sobren. Se guardan al pulsar **Save** y sobreviven al cierre del
41
+ diálogo.
42
+
43
+ ## Pestaña Call — la llamada
44
+
45
+ `Prompt type`, el selector de tools, y los dos textareas de `System` y `Prompt`, con sus historiales. Es el
46
+ mismo juego de campos que el editor de triggers, más los dos botones **Apply Config** y **Fire**.
47
+
48
+ ![La pestaña Call del Playground; Fire está deshabilitado hasta aplicar la config](../images/playground-call.png)
49
+
50
+ ## Pestañas IN y OUT
51
+
52
+ El Playground **no reutiliza el flujo de la pestaña principal**: cuenta sólo los mensajes producidos desde
53
+ que lo abriste, y los reparte en dos vistas.
54
+
55
+ - **IN** — la traza de entrada: qué recibió el backend y qué le mandó al modelo. Verás
56
+ `[Playground] type/llm/system/prompt/tools`, cada `[Tool call]` con sus argumentos y cada `[Tool result]`
57
+ con lo que devolvió. Es la vista que usas para entender **por qué** el modelo contestó lo que contestó.
58
+
59
+ ![La pestaña IN, con el prompt YA RENDERIZADO que recibió el modelo](../images/playground-in.png)
60
+
61
+ Fíjate en la línea `prompt:`: es la plantilla **ya renderizada**. Ahí se ve que `{{ metadata.name }}` se
62
+ convirtió en `api-gateway`… y también que el manifiesto **no** viaja con ella. Es la comprobación más
63
+ rápida de que tu plantilla dice lo que crees que dice.
64
+
65
+ - **OUT** — la respuesta: `[Playground] response` y el recuento de tokens y pasos.
66
+
67
+ ![La pestaña OUT, con la respuesta del modelo y el gasto de tokens](../images/playground-out.png)
68
+
69
+ Al cerrar el diálogo, estos mensajes **se retiran del flujo principal**: no ensucian la pestaña.
70
+
71
+ ## Advertencias importantes
72
+
73
+ El Playground no ejecuta exactamente el mismo camino que un trigger real. Estas cuatro diferencias son las
74
+ que más tiempo hacen perder:
75
+
76
+ ### 1. En modo Business, el payload ES el prompt
77
+
78
+ En modo Business el backend **descarta el `Prompt` de la pestaña Call** y usa el contenido del textarea de
79
+ evento como prompt, renderizado con jinja sobre un contexto vacío. Si estás probando un trigger de negocio,
80
+ escribe la pregunta en el textarea del evento, no en el campo Prompt.
81
+
82
+ ### 2. En modo Business, cambiar Space/Type rompe la prueba
83
+
84
+ El backend sólo redirige un evento al Playground si llega con **`space: launch` y `type: immediate`** —los
85
+ valores por defecto—. Si los cambias, el evento deja de ir al Playground y pasa a evaluarse contra tus
86
+ triggers de negocio **reales**. Y si además el par que has escrito no es uno de los tres a los que el canal
87
+ está suscrito (`customers.status`, `branches.status`, `launch.immediate`), no llegará a ninguna parte y no
88
+ verás absolutamente nada.
89
+
90
+ En modo Artifact el problema no existe: el `Fire` usa `launch`/`immediate` sin preguntar.
91
+
92
+ ### 3. El `Prompt type` que elijas no siempre manda
93
+
94
+ En modo Artifact, el backend decide el tipo de prompt **según tengas o no texto en el campo Prompt**: si hay
95
+ prompt lo trata como `jinja`, y si está vacío como `artifact`. El selector `Prompt type` de la pestaña Call
96
+ no se respeta en esta ruta. Para probar el modo `artifact` puro, **vacía el campo Prompt**.
97
+
98
+ ### 4. El evento simulado siempre es `ADDED`
99
+
100
+ El objeto que inyectas se le entrega al modelo como un evento `ADDED`, sea cual sea el `K8s Event` que
101
+ selecciones. Ese selector se guarda con el estado del Playground, pero no cambia la simulación.
102
+
103
+ ## Diferencias de motor con un trigger real
104
+
105
+ | | Trigger real | Playground |
106
+ |---|---|---|
107
+ | Salida | Estructurada (findings, PSS, informe) | **Texto libre** |
108
+ | `autoTools` | Entrega el catálogo entero al modelo | Hace una **llamada previa** al modelo para que elija las tools, y sólo le pasa esas |
109
+ | Si el modelo no responde texto | Es un error | Hace una **segunda llamada** resumiendo los resultados de las tools |
110
+
111
+ La primera diferencia es la que importa: en el Playground **nunca vas a ver un finding**. Sirve para validar
112
+ que el prompt lleva al modelo por el camino correcto y que las tools devuelven lo que esperas; el formato
113
+ estructurado sólo lo verás cuando lo exportes a un trigger y lo dispares de verdad.
114
+
115
+ La segunda explica por qué `autoTools` sale más caro en el Playground que en producción: hay una llamada
116
+ extra, cuya elección de tools se te muestra como `[Auto tools] selected: ...`.
117
+
118
+ ## Llevarse el resultado
119
+
120
+ Los cuatro botones de abajo a la izquierda:
121
+
122
+ | Botón | Qué hace |
123
+ |--------------|------------------------------------------------------------------------------------|
124
+ | **Import** | Carga en el Playground la configuración de **un trigger existente**, eligiendo trigger y versión. Sobrescribe lo que tengas. |
125
+ | **Export** | Convierte el Playground en un trigger: **New trigger** (nuevo, con la versión `v1` activada) o **Add version** (añade una versión desactivada a un trigger existente). Cierra el diálogo. |
126
+ | **Upload** | Carga la configuración desde un fichero JSON local. |
127
+ | **Download** | Guarda la configuración actual en `pinocchio-playground-AAAA-MM-DD.json`. |
128
+
129
+ Y a la derecha, **Save** (persiste el estado y los historiales) y **Cancel** (cierra sin guardar).
130
+
131
+ > ⚠️ **Export → New trigger no se lleva el Kind ni el K8s Event.** El trigger nuevo se crea con el tipo
132
+ > (`artifact` o `business`) y la versión, pero sin `kind` y sin `k8sEvent`, así que un trigger `artifact`
133
+ > recién exportado **no casará con ningún evento** hasta que lo abras en **Config → Trigger** y le pongas el
134
+ > kind a mano. Está recogido en [Límites conocidos](../admin/06-limits.md).