Ir al contenido

Revisión adversarial de código con IA: método, evidencia y límites

La revisión adversarial de código es la práctica de que una parte produzca un cambio y otra parte, deliberadamente independiente, lo ataque: que busque lo que está mal, lo que falta o lo que no está probado, y lo diga con evidencia. Con asistentes de IA, la parte independiente suele ser un modelo de otra familia que el que escribió el código. También se la llama revisión entre modelos (cross-model), segunda opinión o maker-checker; la práctica es la misma.

Esta página explica la práctica por sí misma. Sirve aunque nunca instales nada. Disensor aparece al final, donde la práctica deja un problema que no resuelve sola.

Por qué separar a quien escribe de quien ataca

Sección titulada «Por qué separar a quien escribe de quien ataca»

Un modelo que revisa su propio trabajo comparte los puntos ciegos de ese trabajo. Los supuestos que no cuestionó al escribir son los que no cuestiona al releer, así que pedirle que se autocritique produce autocrítica, no revisión. La solución no es un revisor más capaz. Es un revisor que falle en otro lugar: otro entrenamiento, otros hábitos, otros errores. Por eso la familia del modelo que revisa importa más que su capacidad bruta.

La separación también cambia el incentivo. Al revisor no se le pregunta si el cambio es aceptable. Se le pide que intente romperlo, que cite dónde y que ordene lo que encontró. No tiene interés en que el cambio pase, y la consigna tiene que decirlo. Adversarial no quiere decir hostil; quiere decir que la carga de la prueba está del lado del cambio.

El ejemplo de abajo es un evento de revisión real, anonimizado; viene con disensor como archivo de muestra, pero la práctica es la misma con cualquier herramienta.

  1. Congelar lo que se revisa. Un plan, un diff o una decisión de diseño. En el ejemplo, un diff que agrega un flujo de exportación a un servicio.
  2. Entregarlo al revisor con una consigna. La consigna dice qué atacar (corrección, caminos de error, pruebas que faltan, comportamiento no definido, seguridad) y qué evidencia traer: archivo y línea, no impresiones. Conviene versionarla, para poder decir después qué se pidió realmente.
  3. El revisor devuelve hallazgos. Tres, en el ejemplo: el flujo de escritura no se libera si la consulta falla a mitad de camino; no hay un límite definido para exportaciones que superan el rango permitido; la exportación no valida el rango de fechas que recibe.
  4. El generador verifica cada uno. Contra el repositorio, ejecutando algo, o contra una especificación. No por la palabra del revisor: un revisor de otra familia está decorrelacionado, no tiene razón.
  5. Cada hallazgo termina en un estado. Corregido y verificado; registrado como deuda con dueño; decisión explícita del dueño; refutado con evidencia; refutado por juicio; o escalado a una persona. En el ejemplo, la fuga del flujo se corrigió y quedó cubierta por una prueba específica; la validación de fechas se refutó con un enlace al objeto de consulta compartido que ya valida el rango; el límite de la exportación se escaló a producto, porque no es una decisión técnica.
  6. El ciclo cierra cuando todo hallazgo tiene estado. El acuerdo entre los dos modelos no es la condición de cierre, y el silencio tampoco.

Tratá cada hallazgo como una afirmación a comprobar, venga del modelo que venga.

  • Ubicalo. Leé el camino de código que el hallazgo nombra. Si no nombra ninguno, pedilo antes de hacer cualquier otra cosa.
  • Reproducilo cuando puedas. Una prueba que falla o una corrida que muestra el comportamiento vale más que un argumento. Cuando no lo podés correr, decilo: es parte del resultado, no un detalle para saltear.
  • Decí contra qué verificaste. El repositorio, una ejecución o una fuente externa como una especificación o un advisory. La distinción importa después, cuando alguien pregunte cuánto confiar en el cierre.
  • Corregir no es cerrar. Un hallazgo cierra cuando la corrección se verificó, con una prueba o una segunda pasada, no cuando se aplicó el parche.
  • No confundas acuerdo con evidencia. Que dos modelos converjan en un veredicto no es confirmación. Trabajo reciente sobre revisión de código con varios agentes reporta exactamente esa falla, agentes que acuerdan sin evidencia suficiente, y la trata como estructural (arXiv:2608.18167). La ronda se apoya en desacuerdo fundado en código; el consenso no es su producto.

Un revisor de otra familia produce falsos positivos. Es el precio de la decorrelación, y está bien mientras se manejen a la vista.

  • Refutá con evidencia. Citá el código, la prueba o el documento que contradice el hallazgo, y conservá la cita. Una refutación que solo dice «esto no es un problema» es una opinión contra otra.
  • Separá lo verificable de lo interpretativo. Algunas refutaciones descansan en juicio y no en prueba: un riesgo que se considera aceptable, una convención que el revisor no conocía. Esas las tiene que mirar una persona, justamente porque ninguna evidencia las zanja.
  • No lo descartes en silencio. Un hallazgo descartado es invisible. Un hallazgo refutado, con su evidencia, es algo que una tercera persona puede auditar después y discutir.
  • Corregí el hallazgo, no el remedio. Los revisores proponen parches, y el parche puede estar mal aunque el hallazgo esté bien. Verificá el problema primero y después elegí la corrección.

La revisión adversarial es un control, y todo control tiene un borde.

  • No encuentra lo que ninguno de los dos modelos sabe. Dos puntos ciegos decorrelacionados son menos que uno, no cero.
  • El material revisado puede dirigirse al revisor. Un repositorio puede traer instrucciones apuntadas al modelo que lo lea; salvo que el revisor pase por algo que lo neutralice, hay que asumir que pudo ser conducido.
  • No puede correr lo que no puede correr. En la práctica, lo que más queda sin cerrar no es que el revisor se equivoque; es una verificación que no se pudo ejecutar, con el pipeline siguiendo igual.
  • Algunas decisiones no son técnicas. Un límite de filas, un riesgo aceptado, un cambio de alcance: eso es de una persona, y la ronda tiene que entregárselo en vez de resolverlo por decreto.
  • Cuesta una ronda. Para un cambio trivial el ataque es cómputo perdido; para un cambio que toca lo que no se puede deshacer, es el momento más barato para equivocarse.

Todo lo anterior ocurre adentro de una conversación. El modelo encontró siete cosas; incorporaste cuatro, refutaste dos y escalaste una que nadie podía probar. Después la ventana se cierra y esa información deja de existir. Seis meses más tarde, cuando aparece el bug, nadie puede decir si se sabía.

Un tilde verde no llena ese hueco. Dice que algo corrió y no falló. No dice qué se atacó, qué se refutó y con qué, ni qué sigue descansando en el juicio de alguien.

Un registro útil de una ronda contiene: quién revisó y cuán independiente fue; la consigna que se usó; cada hallazgo con su estado terminal y la evidencia detrás; y, sobre todo, el residuo: lo que la ronda no pudo cerrar sola, listado con nombre. Residuo y no cobertura, porque una lista de lo que se cubrió se lee como sello de calidad, y un sello de calidad invita a dejar de mirar.

Ese registro es lo que disensor define y hace cumplir. Es un archivo JSON versionado en tu repositorio, al lado del código que juzga, validado contra un esquema y un conjunto de reglas, y verificado como gate en el pull request. El gate no corre ningún modelo ni pide claves de API: lee lo que ya está en el repositorio. El comando opcional disensor round puede correr el paso del revisor por vos, con un revisor instalado en tu máquina, y nunca juzga lo que devuelve. Si la práctica de esta página es lo que hacés, o lo que querés hacer, las páginas siguientes explican cómo es el archivo y qué frena el CI, el método detrás y el contrato exacto.