Módulo 6: Postmortems And Iterating

La cultura blameless: el lenguaje decide si el chequeo importa

Descripción

Las lecciones anteriores dieron el principio (investiga el sistema, no a la persona) y la mecánica (buildPostmortem(), con su chequeo automático de nombres propios). Esta lección conecta las dos con una idea incómoda pero central: el chequeo automático de la lección 3 solo detecta una forma de culpar a alguien —nombrarlo explícitamente—. La cultura blameless de un equipo se decide, en la práctica, en el lenguaje que usa antes de que ese chequeo automático entre en juego: en cómo un ingeniero describe, con sus propias palabras y bajo presión de tiempo, qué pasó. Un equipo puede pasar el chequeo de buildPostmortem() a la perfección y, aun así, tener una cultura de postmortems que la gente teme completar con honestidad, si el lenguaje que usa —sin nombrar a nadie explícitamente— sigue apuntando, de forma inconfundible para cualquiera del equipo, a una persona específica.

Conexión con el módulo. Esta lección reutiliza buildPostmortem() exactamente como quedó en la lección 3, sin cambiarle una línea, y la corre sobre dos versiones del mismo incidente: el borrador que alguien escribió bajo la presión del momento, y la versión final que pasó por revisión. La comparación entre las dos es el argumento completo de esta lección.

Una analogía: la caja negra, y la diferencia entre el reporte inicial y el informe final

En una investigación de aviación real, el primer reporte que se escribe —a veces horas después del incidente, con la adrenalina todavía presente— casi nunca es el informe final que se publica. El reporte inicial suele tener lenguaje más crudo, más cercano a "el piloto hizo X" o "el controlador no avisó Y a tiempo" — porque quien lo escribe todavía está procesando lo que vio, no reconstruyendo con calma el sistema completo. El proceso formal de investigación existe, en buena parte, para que ese primer reporte pase por una revisión que lo convierte en un documento sistémico: la misma información, pero reescrita para que apunte a las condiciones que permitieron el incidente, no a la persona que estaba presente cuando ocurrió.

El postmortem de recommendations sigue exactamente ese mismo camino. El borrador de las 14:20 —escrito minutos después de que el kill switch ya estuviera activado, con el incidente todavía fresco— nombra a Diego. La versión final —revisada con más calma, horas después— dice lo mismo sobre los hechos, pero apunta al proceso de aprobación. Ninguna de las dos versiones miente; la diferencia es, exclusivamente, dónde pone el foco.

Ejemplo trabajado: dos versiones del mismo incidente, con buildPostmortem() sin cambios

// buildPostmortem: SIN cambios respecto a la leccion 3.
function buildPostmortem(incident) {
  const timeline = [...incident.events].sort((a, b) => a.time.localeCompare(b.time));
  const namedPeople = ['Ana', 'Bruno', 'Carla', 'Diego', 'Elena'];
  const factors = incident.contributingFactors.map((description) => {
    const redFlag = namedPeople.some((name) => description.includes(name));
    return { description, redFlag };
  });
  const blameless = factors.every((f) => !f.redFlag);
  return { incidentName: incident.name, timeline, contributingFactors: factors, actionItems: incident.actionItems, blameless };
}

const events = [
  { time: '13:58', what: 'El rollout avanza de canary (1%, limpio) a la etapa de 10%.' },
  { time: '14:12', what: 'guardrailWatch() marca HALT en la etapa de 10%: p95Latency=910ms, techo 800ms.' },
];
const actionItems = [{ owner: 'recommendations-team', action: 'Rediseñar la llamada al motor de recomendaciones.', due: '2026-08-08' }];

// Version 1: el borrador ORIGINAL que alguien del equipo escribio a las
// 14:20, todavia buscando "quien" en vez de "que" del sistema fallo.
const draftWithBlame = {
  name: 'recommendations: borrador inicial (14:20)',
  events,
  contributingFactors: [
    'La llamada al motor de recomendaciones es sincrona y bloqueante; no escala al volumen de la etapa de 10%.',
    'Diego aprobo el avance a la etapa de 10% sin confirmar que se habia corrido una prueba de carga.',
  ],
  actionItems,
};

// Version 2: la MISMA causa raiz, reescrita en la revision de postmortem
// (leccion 3) senalando el proceso, no a la persona que sigio ese proceso.
const draftBlameless = {
  name: 'recommendations: version final del postmortem',
  events,
  contributingFactors: [
    'La llamada al motor de recomendaciones es sincrona y bloqueante; no escala al volumen de la etapa de 10%.',
    'El criterio de avance de la rampa no exigia confirmar una prueba de carga equivalente al volumen de la siguiente etapa.',
  ],
  actionItems,
};

console.log('=== Version 1: borrador con un factor que nombra a una persona ===');
const pm1 = buildPostmortem(draftWithBlame);
pm1.contributingFactors.forEach((f, i) => console.log((i + 1) + '. [' + (f.redFlag ? 'RED FLAG' : 'blameless') + '] ' + f.description));
console.log('Postmortem blameless: ' + pm1.blameless);

console.log('\n=== Version 2: la misma causa, reescrita como factor de proceso ===');
const pm2 = buildPostmortem(draftBlameless);
pm2.contributingFactors.forEach((f, i) => console.log((i + 1) + '. [' + (f.redFlag ? 'RED FLAG' : 'blameless') + '] ' + f.description));
console.log('Postmortem blameless: ' + pm2.blameless);

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== Version 1: borrador con un factor que nombra a una persona ===
1. [blameless] La llamada al motor de recomendaciones es sincrona y bloqueante; no escala al volumen de la etapa de 10%.
2. [RED FLAG] Diego aprobo el avance a la etapa de 10% sin confirmar que se habia corrido una prueba de carga.
Postmortem blameless: false

=== Version 2: la misma causa, reescrita como factor de proceso ===
1. [blameless] La llamada al motor de recomendaciones es sincrona y bloqueante; no escala al volumen de la etapa de 10%.
2. [blameless] El criterio de avance de la rampa no exigia confirmar una prueba de carga equivalente al volumen de la siguiente etapa.
Postmortem blameless: true

Las dos versiones comparten el primer factor —la llamada bloqueante— palabra por palabra, y las dos comparten los mismos eventos y el mismo action item. La única diferencia entre pm1.blameless: false y pm2.blameless: true es cómo está redactado el segundo factor. Esto es, literalmente, el argumento completo de esta lección hecho ejecutable: la cultura blameless de un equipo no se decide en los hechos que investiga —ambas versiones investigan exactamente lo mismo—, se decide en el lenguaje con el que los describe.

Profundización: por qué la revisión del borrador importa tanto como el postmortem final

Vale la pena notar algo que el ejemplo no dice explícitamente pero que es central: nadie estaba mintiendo en el draftWithBlame. A las 14:20, con el incidente recién contenido, es humanamente natural escribir "Diego aprobó el avance sin confirmar la prueba de carga" — es lo que alguien recuerda haber visto, en el orden en que lo vivió. El problema no es que ese borrador sea deshonesto; es que, si se publicara tal cual, sería el documento que el resto del equipo lee, y ese documento entrena —sin que nadie lo diga en voz alta— la próxima vez que alguien tenga que decidir si reportar un incidente con honestidad completa o suavizar su propio rol para no aparecer nombrado.

Esto es exactamente por qué un postmortem blameless de verdad necesita un paso de revisión entre el primer borrador y la versión que el equipo publica y discute. Ese paso no existe para "limpiar" el incidente ni para esconder información —el hecho de que Diego aprobó el avance sigue siendo parte de la timeline, en el evento correspondiente—; existe para asegurarse de que los contributing factors, la sección que explica "por qué pasó esto", apunten siempre al sistema, incluso cuando el primer instinto de quien escribe, bajo presión, apuntó a una persona. La revisión es la disciplina; el chequeo automático de buildPostmortem() es solo la red de seguridad que atrapa los casos más obvios.

Errores comunes

Publicar el primer borrador sin ninguna revisión, "para que sea más rápido". Qué pasa: el equipo, con ganas de cerrar el incidente pronto, publica el postmortem tal como lo escribió la primera persona que se sentó a documentarlo, sin que nadie más lo revise antes de compartirlo con el resto de la organización. Por qué pasa: después de un incidente estresante, hay presión real por "cerrar el capítulo" rápido, y un paso de revisión se siente como una demora evitable. Cómo detectarlo: si el postmortem que circula tiene el mismo lenguaje, palabra por palabra, que las primeras notas tomadas durante el incidente, probablemente no pasó por ninguna revisión. Cómo corregirlo: como muestra el ejemplo de hoy, la diferencia entre draftWithBlame y draftBlameless es exactamente el trabajo de una revisión — vale la pena el tiempo, incluso si retrasa la publicación por un día.

Asumir que el chequeo automático de nombres es suficiente, y dejar de revisar el lenguaje con criterio humano. Qué pasa: un equipo confía completamente en que buildPostmortem() (o una herramienta equivalente) va a atrapar cualquier problema de lenguaje, y deja de leer los factores contribuyentes con atención humana antes de publicarlos. Por qué pasa: automatizar un chequeo se siente como resolver el problema por completo, y es fácil olvidar que la herramienta solo captura el caso más literal (un nombre propio exacto). Cómo detectarlo: si un factor contribuyente dice algo como "la persona de guardia esa noche no siguió el runbook" —sin nombre propio, pero perfectamente identificable para cualquiera que sepa quién estaba de guardia— y pasa el chequeo automático sin que nadie lo cuestione, el chequeo dio una falsa sensación de seguridad. Cómo corregirlo: usa el chequeo automático como una primera red, no como el único filtro — la pregunta humana de la lección 2 ("¿esta frase seguiría siendo cierta con cualquier otra persona calificada en esa posición?") sigue haciendo falta, sin importar qué tan buena sea la herramienta.

Tratar la revisión del lenguaje como "suavizar" el incidente para que se vea mejor. Qué pasa: alguien objeta el proceso de revisión, argumentando que reescribir "Diego aprobó sin confirmar" como "el criterio de avance no exigía confirmar" es minimizar lo que pasó, o evitar que el equipo aprenda de un error real. Por qué pasa: es fácil confundir "cambiar dónde apunta la explicación" con "cambiar qué tan grave se ve el problema" — cuando en realidad, como viste en la lección 2, la versión sistémica suele exigir un arreglo más profundo, no uno más leve. Cómo detectarlo: si alguien argumenta que la versión blameless "le quita peso" al incidente, pídele que compare los dos action items resultantes — en este ejemplo, ambas versiones producen exactamente el mismo action item (arreglar la llamada al motor), porque el hecho de fondo no cambió. Cómo corregirlo: la revisión de lenguaje no cambia la gravedad del incidente ni el compromiso de arreglarlo — cambia si el próximo incidente similar se va a poder prevenir mejorando el sistema, o si va a depender, de nuevo, de que una persona distinta "tenga más cuidado".

Ejercicios

Ejercicio 1 — Encuentra el red flag disfrazado. El siguiente factor contribuyente pasaría el chequeo de buildPostmortem() (no contiene ningún nombre de namedPeople), pero sigue violando el espíritu de la cultura blameless: "La persona que estaba de guardia esa noche, la unica con acceso al panel de kill switches en ese momento, tardo mas de lo esperado en activarlo." Explica por qué, aunque pase el chequeo automático, este factor no es realmente blameless, usando la pregunta correctora de la lección 2.

Ver solución

Aunque no contiene ningún nombre propio de la lista namedPeople, la frase es perfectamente identificable: dice explícitamente que había una sola persona con acceso al panel en ese momento, así que cualquiera del equipo que sepa quién estaba de guardia esa noche sabe exactamente a quién se refiere. Aplicando la pregunta correctora de la lección 2 —"¿esta frase seguiría siendo cierta con cualquier otra persona calificada en esa posición?"—, la respuesta revela el problema real: el hecho de que solo una persona tuviera acceso al kill switch en ese momento es, en sí mismo, un factor sistémico —de guardia insuficiente o de acceso mal distribuido—, no un comentario sobre qué tan rápido reaccionó esa persona específica. Una versión mejor: "El acceso al panel de kill switches estaba limitado a una sola persona por turno de guardia, sin un mecanismo de respaldo si esa persona tardaba en estar disponible."

Ejercicio 2 — Predicción sobre el efecto a largo plazo. Un equipo publica, sistemáticamente durante un año, postmortems que —aunque pasan el chequeo automático de nombres— siguen usando lenguaje que deja claro, para cualquiera del equipo, quién estuvo involucrado en cada error. ¿Qué crees que pasaría, con el tiempo, con la honestidad de los reportes de incidentes menores en ese equipo (los que todavía no llegan a ser Sev-2)?

Ver solución

Es razonable esperar que, con el tiempo, la gente empiece a reportar menos incidentes menores, o a describirlos de forma más vaga y defensiva —porque aprendió, por experiencia, que "hacerse visible" en un postmortem tiene un costo social, aunque nadie lo diga en voz alta ni se use ningún nombre propio explícito—. Esto es particularmente peligroso porque los incidentes menores, bien documentados, son exactamente los que permiten arreglar un sistema antes de que produzca un incidente mayor como el de recommendations. Una cultura que técnicamente pasa el chequeo de nombres pero sigue señalando personas de forma indirecta puede terminar, paradójicamente, con menos información real sobre los problemas del sistema que una que nunca automatizó nada, pero sí construyó confianza genuina.

Ejercicio 3 — Escribe la política del equipo. En 2-3 frases, redacta una regla de revisión de postmortems que el equipo de Mercado podría adoptar para evitar tanto los nombres explícitos como el lenguaje indirecto que sigue señalando a una persona identificable.

Ver solución

Una regla razonable: "Todo postmortem pasa por al menos una revisión de una persona que no participó directamente en el incidente, antes de publicarse. Esa revisión aplica la prueba de la lección 2 a cada contributing factor: '¿esta frase seguiría siendo cierta con cualquier otra persona calificada en la misma posición?' — si la respuesta es no, o si el factor solo tiene sentido porque una persona específica estuvo involucrada, se reescribe para apuntar al proceso o al sistema antes de publicarse." Esta regla ataca exactamente el punto ciego de este ejercicio: un chequeo automático de nombres es necesario pero no suficiente, y una revisión humana con un criterio explícito es lo que cierra la diferencia.

Resumen y siguiente paso

En esta lección viste, con el mismo buildPostmortem() de la lección 3 sin cambiar una línea, que la diferencia entre un postmortem blameless y uno que no lo es puede estar en una sola frase reescrita — la comparación entre draftWithBlame (blameless: false) y draftBlameless (blameless: true) demostró que ambos documentan exactamente el mismo hecho, y que la cultura blameless de un equipo se decide en cómo se describe ese hecho, no en qué hecho se documenta. También viste que el chequeo automático de nombres propios es una red de seguridad útil, pero no sustituye la revisión humana que detecta lenguaje indirecto que sigue señalando a una persona sin nombrarla.

Antes de avanzar deberías poder: explicar por qué el primer borrador de un postmortem, escrito bajo presión, no suele ser blameless todavía; identificar lenguaje que señala a una persona sin usar su nombre; y defender por qué un paso de revisión humana sigue siendo necesario aunque exista un chequeo automatizado.

Con esto cierras el arco del postmortem dentro de este módulo: sabes qué es blameless (lección 2), cómo se estructura en código (lección 3), y por qué el lenguaje decide si esa estructura cumple su propósito (esta lección). La lección 5 da un paso atrás y ubica todo esto dentro de un marco más grande: el loop ship → measure → learn, donde el postmortem es, precisamente, la pieza de "aprender" que alimenta el siguiente ciclo.

Recursos

  • John Allspaw, "Blameless PostMortems and a Just Culture" (Etsy, Code as Craft, 2012) — etsy.com/codeascraft/blameless-postmortems. El argumento original sobre por qué el lenguaje de un postmortem, más que sus reglas formales, es lo que construye —o destruye— la confianza de un equipo para reportar con honestidad. En inglés.
  • Google SRE Book, Capítulo 15, "Postmortem Culture: Learning from Failure" — sre.google/sre-book/postmortem-culture. La sección sobre cómo Google revisa y comparte postmortems dentro de la organización —incluyendo el "Postmortem of the Month"— como mecanismo para normalizar el lenguaje sistémico en toda la empresa. En inglés.
  • Atlassian, "How to run a blameless postmortem" — atlassian.com/incident-management/postmortem/blameless. Recomendaciones prácticas sobre cómo facilitar la conversación del postmortem para que el lenguaje sistémico surja de forma natural, en vez de depender solo de una revisión posterior. En inglés.