Files
aleleba 18efe3fa5d Phase 6.4.25b: 13 corrective seeds for gate 5 diagnosis, rebuild training mix
Diagnosis (audited gate5 full-mode transcripts, PNGs, and holdout payloads):
the dominant failure mode was NOT rubro/content coverage but a tool-call
formatting bug -- after diagnosing an exception, the model often writes its
retry as literal <tool_call> XML text embedded in reasoning instead of a
structured call, so it's silently dropped (9/10 gate5 full-mode prompts hit
this on their final captured turn). Reasoning length before a tool call
correlates with the failure (malformed-turn mean 1895 chars vs 364 for
well-formed turns), while the training mix's own tool-calling turns never
exceed 1630 chars and export_shape turns never exceed 485.

Corrective seeds (group D, grupo 'D', short 1-3 sentence diagnosis +
immediate well-formed retry, to avoid reinforcing long-reasoning risk):
- 9 seeds across fresh rubros (lavanderia, zapateria, optica, jugueteria,
  papeleria, peluqueria, floreria, heladeria, cerrajeria) covering distinct
  real API errors: .color on Text, addFlexLayout on non-Board, findShapeById
  2-arg, createText() no-arg, textAlign, typography.setFont, createBoolean
  null, appendChild no-arg, uploadMediaUrl network rejection.
- 4 seeds reinforcing uploadMediaUrl over the exact penpot.importImage
  variant the model actually invents (distinct from the one existing seed,
  which only taught the penpotUtils.importImage variant) -- rubros:
  relojeria, guarderia infantil, agencia de viajes, kiosco.

Deliberately avoid ferreteria/gimnasio/veterinaria/panaderia: reserved for
the held-out generalization probe (separate from the frozen gate 5).

Added the newly-verified error strings (t.color, penpot.importImage,
addFlexLayout-on-non-Board) to penpot_errors.md -- all captured live from
this phase's own gate 5 full-mode run, not fabricated.

Rebuilt train_lora2.jsonl (901)/eval_lora2.jsonl (99)/calibration_v2.jsonl
from the 138-seed corpus (07_build_lora2_mix.py) and validated
(06_validate_dataset.py, 0 exceptions, 0 secrets, max 3271 tokens).
2026-08-03 15:44:22 +00:00

11 KiB
Raw Permalink Blame History

Penpot MCP — strings de error reales

Mensajes de error literales que devuelve el servidor MCP de Penpot cuando un execute_code falla.

Por qué existe este archivo. El grupo D de data/raw/seeds/penpot.jsonl (6 seeds de auto-corrección) entrena al modelo a diagnosticar y recuperarse de errores. Si el mensaje de error de un seed es inventado, el modelo aprende a reaccionar a un string que nunca va a ver. Este archivo es la allow-list de mensajes de error: scripts/07_lint_penpot_code.py verifica que todo content de un mensaje tool que representa un error sea un string presente acá, verbatim.

Procedencia. Los mensajes de las secciones 1-4 fueron capturados en vivo por la sesión madre el 2026-07-30, contra el archivo real del usuario, ejecutando el código de la columna "Disparador" vía mcp__penpot__execute_code. Están registrados en PLAN.md § "La API verificada" y en la página de Docmost de la Fase 6 § "Diagnóstico". Los de la sección 5 son mensajes estándar del runtime de JavaScript (V8/SpiderMonkey), deterministas dada la expresión que los produce.

Nota operativa (2026-07-30): al intentar re-capturar estos strings durante el paso 6.1.3, el servidor MCP devolvió No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected. — el plugin del usuario no está conectado. Los mensajes de abajo se conservan tal como los verificó la sesión madre; la re-verificación en vivo queda pendiente y se hace junto con el baseline de la puerta 5 (paso 6.3.17), que también requiere el plugin conectado. Ningún seed puede usar un string de error ausente de este archivo.


1. Propiedad inexistente sobre un objeto no extensible

El objeto Text (y en general los shapes) no es extensible: asignar una propiedad que no está en la interfaz lanza, y la excepción aborta todo el execute_code, no solo esa línea.

Disparador Mensaje verbatim
text.textAlign = 'center' Cannot add property textAlign, object is not extensible
text.color = '#...' Cannot add property color, object is not extensible

Forma del mensaje, generalizable: Cannot add property <nombre>, object is not extensible.

text.color (capturado en vivo, puerta 5 modo full, paso 6.4.25, prompts g5-06-landing-pizzeria y g5-01-boton-primario, 2026-08-03): el modelo asume que Text tiene una propiedad color de nivel superior (analogía con CSS) en vez de usar fills. Es el mismo mecanismo que textAlign —el objeto Text no es extensible— pero para una propiedad distinta, y hoy no hay ningún seed que lo enseñe (el lint ya lo detecta como patrón prohibido, cero ejemplos de recuperación en el corpus).

La propiedad correcta para alineación horizontal de texto es align ('left' \| 'center' \| 'right' \| 'justify'). Ver penpot_api_docs.md Captura 4.

Este es el error más caro del conjunto, porque el modelo suele escribir textAlign por analogía con CSS, en medio de un payload de 80 líneas que construye una landing entera: la excepción tira abajo la composición completa, no solo el título.


2. Función inexistente en el runtime

Disparador Mensaje verbatim
penpotUtils.importImage(url) penpotUtils.importImage is not a function
penpot.importImage(url) penpot.importImage is not a function
typography.setFont(font) sobre un LibraryTypography t.setFont is not a function

penpot.importImage (capturado en vivo, puerta 5 modo full y holdout, paso 6.4.25, 2026-08-03): variante distinta de la ya documentada penpotUtils.importImage — el modelo la invoca directamente sobre el objeto penpot (no sobre penpotUtils), en 1 de los 10 prompts de la puerta 5 modo full (g5-03-card-producto) y en 4 de los 60 del holdout, siempre en prompts que piden "una foto real traída de una URL". El único seed de recuperación existente (grupo D) enseña la variante penpotUtils.importImage, no ésta — de ahí que el modelo no generalizara la lección.

Tres casos distintos, y la diferencia importa pedagógicamente:

  • penpotUtils.importImage nunca existió: no está en la Captura 1 de penpot_api_docs.md, ni import_image está entre las herramientas MCP de este deployment (ver data/schemas/penpot.json). El overview la menciona en prosa (Use the export_shape and import_image tools), lo cual es un anzuelo real: la lista de herramientas que recibe el modelo es autoritativa, la prosa del overview no. La ruta correcta es await penpot.uploadMediaUrl(name, url).
  • typography.setFont sí está en el tipo (penpot_api_docs.md Captura 26) pero no existe en el runtime de esta versión. Es el caso canónico de "la documentación no es el runtime": hay que setear las propiedades de tipografía una por una (fontFamily, fontSize, fontWeight, lineHeight). Notar que el mensaje dice t.setFont, con el nombre de la variable local — no typography.setFont.

3. Uso de un retorno null sin chequear

La familia de errores más frecuente y la que produce la falla reportada por el usuario. Todas las llamadas de creación y de búsqueda pueden devolver null; usar el resultado sin chequearlo produce un TypeError en la línea siguiente, lo que hace que el stack apunte al síntoma y no a la causa.

Disparador Mensaje verbatim
const t = penpot.createText(); t.characters = 'Hola' Cannot set properties of null (setting 'characters')
const s = penpotUtils.findShapeById(page, id); s.x = 10 Cannot read properties of null (reading 'x')
const s = penpotUtils.findShapeById(idInexistente); return s.fills Cannot read properties of null (reading 'fills')
const b = penpot.createBoolean('union', []); b.name = 'x' Cannot set properties of null (setting 'name')
const r = penpot.createRectangle(); const f = r.addFlexLayout(); f.dir = 'row' Cannot set properties of null (setting 'dir')

addFlexLayout() sobre un Rectangle (capturado en vivo, puerta 5 modo full, g5-02-navbar-flex, 2026-08-03): addFlexLayout() solo existe en Board. Llamarlo sobre un Rectangle (o sobre un Board que ya tiene un layout asignado) devuelve null en silencio en vez de lanzar; el error real aparece recién en la línea siguiente al intentar setear una propiedad del layout inexistente. Hay que chequear el retorno de addFlexLayout() antes de usarlo, igual que con createText().

Forma del mensaje, generalizable: Cannot read properties of null (reading '<prop>') y Cannot set properties of null (setting '<prop>').

Las tres fuentes de null que los seeds deben enseñar a chequear:

  1. penpot.createText() sin argumento o con ''null. Con un string no vacío → Text. El ejemplo de la documentación oficial (penpot_api_docs.md Captura 6) usa la forma sin argumento.
  2. penpotUtils.findShapeById(page, id)la forma de dos argumentos no lanza: devuelve null en silencio, porque findShapeById tiene aridad 1 y el segundo argumento se descarta mientras el primero (un Page, no un string) no matchea ningún id. La forma correcta es penpotUtils.findShapeById(id).
  3. penpot.createBoolean(type, shapes) y penpot.createShapeFromSvg(svg) declaran null | T en su firma (Capturas 10 y 11).

4. Fallos de red y de subida de medios

Disparador Mensaje verbatim
await penpot.uploadMediaUrl('foto', 'https://dominio-inexistente.invalid/a.jpg') Error uploading media

uploadMediaUrl devuelve una Promise; el fallo llega como rechazo, así que hay que envolverlo en try/catch con await adentro del try (un .catch() colgado de la promesa sin await deja el resto del payload corriendo con undefined).


5. Errores estándar del runtime de JavaScript

Deterministas dada la expresión, no específicos de Penpot. Se listan porque aparecen en los seeds del grupo D y el lint los tiene que aceptar.

Disparador Mensaje verbatim
undefined.foo Cannot read properties of undefined (reading 'foo')
shapes.map(...) donde shapes es null Cannot read properties of null (reading 'map')
JSON.parse('{') Unexpected end of JSON input
board.appendChild() sin argumento Cannot read properties of undefined (reading 'id')

6. Errores a nivel de servidor MCP (no de execute_code)

No son excepciones de JavaScript: los devuelve el servidor MCP antes de llegar al plugin. No pueden aparecer como resultado de un execute_code en un seed, porque no dejan al modelo en una situación de "diagnosticá tu código" — significan que no hay con qué hablar.

Situación Mensaje verbatim
El plugin de Penpot no está abierto / no está conectado al servidor MCP No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected.

Capturado en vivo el 2026-07-30 durante el paso 6.1.3 de esta fase.


7. Fallos SILENCIOSOS — no producen ningún error

La categoría más peligrosa, y la razón por la que el invariante R8 (leer de vuelta el efecto) existe. Estas operaciones no lanzan, no devuelven null, y dejan el diseño mal. Un modelo que solo maneja excepciones no las detecta nunca.

Operación Comportamiento real Cómo detectarlo
flex.horizontalSizing = 'auto' Se lee de vuelta como 'auto' pero el board no crece (200×200 sigue 200×200 tras insertar un hijo de 300 de ancho). Bug conocido penpot#8520 Leer board.width tras insertar; hacer board.resize() a mano
layoutChild.zIndex = 10 Se lee de vuelta como 0. Se ignora Leer layoutChild.zIndex; usar el orden del array children para z-order
text.letterSpacing = '-0.02' Se lee de vuelta como "0". Los valores negativos en unidades tipo em se descartan Leer text.letterSpacing; usar unidades tipo px ('-1')
penpot.createRectangle() sin asignar fills Queda con el fill por defecto #B1B2B5 — el gris exacto del problema reportado Invariante R1: fills explícito en todo shape creado
penpotUtils.findShapeById(page, id) Devuelve null, sin lanzar Chequear el retorno antes de usarlo
board.layout undefined; 'layout' in shape === false. La propiedad no existe Usar board.flex / board.grid, que devuelven null cuando no hay layout
generateStyle(shapes, {withChildren: true}) La opción real es includeChildren; withChildren se ignora y el CSS sale sin los hijos Ver penpot_api_docs.md Captura 8
grid.appendChild(shape, 0, 0) El 0 se clampea a 1: se lee de vuelta [1,1]. Los índices son 1-based Leer shape.layoutCell.row / .column
text.resize(w, h) Setea growType a 'fixed' en silencio; el texto desborda su caja Invariante R6: restaurar growType tras resize()
Leer text.width justo después de setear characters El auto-sizing no es inmediato: se lee 1 Dormir ~120 ms antes de leer el bounding box