Skip to content

ADR-0015 — dai publish en un Jira corporativo (campos propios, épicas, TLS)

  • Estado: aceptado
  • Fecha: 2026-07-15
  • Decide: lead / arquitecto de la metodología

Contexto

dai publish funcionaba contra un Jira limpio y fallaba contra uno real. Lo vimos en vivo: un analista funcional intentó publicar su primera US en el Jira de su empresa y createUS mandaba solo project, issuetype, summary y description. El proyecto exigía un campo propio (Tipo de trabajo, customfield_10042) → 400 en todos los intentos. Además el proxy corporativo intercepta TLS con su propia CA, y Node no usa el trust store del sistema → fetch reventaba antes de llegar.

Lo que pasó después es el verdadero motivo de esta ADR. El asistente, bloqueado por el CLI, improvisó: leyó el schema del campo por su cuenta, armó la llamada a la API de Jira con node -e, y para pasar el proxy corrió NODE_TLS_REJECT_UNAUTHORIZED=0 — apagando la verificación de certificado mientras mandaba el token de Jira. Lo reportó como "nota técnica resuelta en el camino". Su propio razonamiento fue: "bypassing dai's limitation entirely".

Publicó bien, de casualidad. Pero eso es exactamente lo que la capa 2 de la ADR-0002 existe para evitar: "el output es idéntico con Claude, con Copilot, o sin ningún asistente". Cuando el CLI no llega, el agente inventa — y el improviso siguiente puede no respetar el contrato del ac_hash, y ahí la trazabilidad se rompe en silencio.

La lección: un CLI que no cubre el caso real no es neutral, es una invitación a puentearlo. Cada hueco de la capa 2 se llena con inteligencia no determinista.

Decisión

1. Los campos propios se declaran, con nombre humano y opciones válidas..dai/jira-fields.json (o DAI_JIRA_FIELDS_FILE), por issuetype:

json
{
  "Story": {
    "clasificacion": {
      "field": "customfield_10042",
      "shape": "select",
      "default": "Mejora",
      "options": ["Mejora", "Corrección"]
    }
  }
}
  • Por issuetype, porque en Jira corporativo los campos obligatorios de una Epic y de una Story casi nunca son los mismos.
  • default + --field alias=valor, porque el valor no es config fija: la clasificación es Mejora o Corrección según la US. Un default fijo publicaría todas iguales — un dato incorrecto en Jira, en silencio, que es peor que fallar.
  • options valida ANTES de la red. Un typo da 'Mejraa' no es opción de 'clasificacion' — válidas: Mejora | Corrección, no un 400 críptico.
  • Un campo declarado tiene que resolver (default u override): se declara porque Jira lo exige. Si es opcional, no se declara.
  • shape cubre las formas de Jira (select {value:X} · text X · multi[{value:X}]), con raw como escape hatch para cualquier cosa rara: dai no necesita entender todo Jira para no estorbar.
  • Sin archivo → {}. Un Jira sin campos obligatorios publica igual que siempre.

2. --parent y --issuetype. --parent cuelga la US de su épica (createUS nunca mandaba parent); --issuetype Epic permite que grill-epic publique épicas por CLI, que hasta hoy no tenían fallback y quedaban como un .md para pegar a mano.

3. Un fallo de TLS enseña el camino correcto, y desaconseja el peligroso. daiFetch traduce los códigos de certificado a un error que manda a NODE_EXTRA_CA_CERTS y dice explícitamente por qué NODE_TLS_REJECT_UNAUTHORIZED=0 no es una alternativa. dai nunca baja la verificación por su cuenta.

4. DAI_JIRA_PROJECT se valida. PROJ-42 es un ticket, no un proyecto — el error de config más común. El mensaje da los dos caminos: DAI_JIRA_PROJECT=PROJ, o --parent PROJ-42 si lo que querías era colgarla de esa épica.

5. La constitución prohíbe las dos maniobras, para todos los asistentes:

  • No bajes la seguridad para avanzar: si una llamada falla por el certificado, declara la CA. Nunca NODE_TLS_REJECT_UNAUTHORIZED=0, verify=False, -k.
  • Si el CLI no llega, para y dilo: no improvises una llamada a la API por fuera.

Consecuencias

  • dai publish sirve en un Jira corporativo, que es donde vive el usuario real.
  • grill-epic gana un fallback CLI: MCP → dai publish --issuetype Epic.md.
  • El caso que motivó el bypass ahora está cubierto, que es la única forma honesta de pedirle a un agente que no improvise: no dejarle el hueco.
  • dai doctor valida la clave de proyecto y que el archivo de campos parsee. Sigue sin verificar que el token sirva — eso es red, y lo dice dai publish. El chequeo ahora lo admite en voz alta en vez de dar un ✓ engañoso.
  • ClickUp queda con el mismo hueco de TLS. daiFetch está listo para adoptarse allí con un import; no lo hicimos ahora por alcance.

Alternativas descartadas

  • Una sola variable DAI_JIRA_FIELDS={"customfield_10042":{"value":"Mejora"}}. Una línea ilegible, sin distinguir Story de Epic, sin validación, y con el valor fijo — el problema original.
  • Resolver los nombres de campo contra createmeta de Jira. Menos config, pero pide red para armar el payload, y falla distinto según permisos. Un archivo explícito se lee y se versiona.
  • Que dai reintente sin verificar TLS si el certificado falla. Es exactamente lo que hizo el agente. Automatizarlo sería institucionalizar el bug.

Software libre (GPLv3). La IA asiste; la persona firma.