Artefacto · Validador · Gate de CI
Lo que la revisión
no cerró, queda escrito.
Disensor emite, valida y hace cumplir en CI la declaración de residuo: el registro de cómo terminó cada evento de revisión adversarial y, sobre todo, de lo que el ciclo no pudo cerrar por sí mismo.
Instalación
pip install disensorDespués, disensor init en la raíz del repo. No pide claves de API ni manda código a ningún servicio.
01
La declaración lista residuo, no cobertura.
Dirige el escrutinio del revisor humano en lugar de leerse como sello de calidad. Es lo contrario de un badge verde: no dice cuánto se revisó, dice qué quedó sobre la mesa y a nombre de quién.
02
Desacuerdo controlado
Dentro de un evento el ciclo es lineal: el revisor ataca una vez. La herramienta no lo orquesta — define y hace cumplir el artefacto con el que ese ciclo termina.
- 01
Genera
Un modelo produce el plan o el diff que se va a mergear.
- 02
Ataca
Un modelo de otra familia lo revisa con una consigna adversarial. La familia distinta es el punto: dos modelos del mismo linaje fallan en los mismos lugares.
- 03
Verifica
El generador contrasta cada hallazgo contra el repositorio o la ejecución y lo lleva a un estado terminal: incorporado, deuda registrada, decisión del dueño, refutado o escalado.
- 04
Declara
El ciclo cierra cuando todo hallazgo llegó a un estado terminal. Lo que no cerró se escribe, se versiona y viaja con el commit.
03
El artefacto
Un JSON versionado en el repositorio, junto al código que juzga. Registra los actores y su familia, cada hallazgo con su estado terminal y las métricas del evento.
El bloque que importa es residue. En este evento real, los 3 hallazgos cerraron —los números dan— y aun así dejaron 3 ítems de residuo, uno por cada clase. Residuo no es lo que quedó abierto: es lo que descansa sobre el juicio de alguien.
No corre modelos, no pide claves de API en CI y ningún código sale del repositorio: valida un archivo que ya está versionado.
- r1Escalado sin decisiónatención humana
El limite de filas de la exportacion quedo escalado a producto y sin resolver al cierre del ciclo.
- r2Refutación del principal
Refutacion con enlace al objeto de consulta compartido, para que el revisor humano pueda no darla por buena.
- r3Gap de ejecuciónatención humana
El comportamiento bajo concurrencia real no se probo porque el entorno de desarrollo no la reproduce.
| total_findings | 3 |
|---|---|
| incorporated | 1 |
| debt_recorded | 0 |
| owner_decision | 0 |
| refuted_verifiable | 1 |
| refuted_interpretive | 0 |
| escalated_open | 1 |
{
"schema": "residue/v0.2",
"profile": "full",
"event": {
"event_id": "7c8d9e0f-1a2b-4c3d-8e5f-6a7b8c9d0e1f",
"created_at": "2026-07-15T18:40:00-03:00",
"repository": "https://github.com/ejemplo/sistema-interno",
"pr": "https://github.com/ejemplo/sistema-interno/pull/214",
"base_commit": "c0ffee1",
"head_commit": "beef042",
"gate": "diff",
"criticality_level": "B",
"abbreviated_path": {
"used": false
}
},
"actors": {
"generator": {
"family": "anthropic",
"model": "claude-code"
},
"reviewers": [
{
"reviewer_id": "r1",
"family": "openai",
"model": "gpt-5.4",
"prompt_hash": "sha256:1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"confinement": {
"mode": "read_only_by_instruction",
"verified": true,
"verification_method": "clean_git_status"
}
}
],
"human_arbiter": {
"present": true,
"id": "dueno-feature-02"
}
},
"findings": [
{
"id": "h1",
"origin": "r1",
"severity": "major",
"title": "El flujo de escritura no se libera si la consulta falla a mitad de camino",
"description": "En la exportacion, una falla intermedia de la consulta dejaba el flujo de escritura tomado.",
"final_state": "incorporated",
"verification": {
"against": "repository",
"detail": "Confirmado en el codigo del diff: faltaba la liberacion en el camino de error."
},
"fix_verification": {
"type": "specific_test",
"reference": "tests/Exportacion_FallaIntermedia_LiberaFlujo"
}
},
{
"id": "h2",
"origin": "r1",
"severity": "major",
"title": "Limite de filas de la exportacion sin definir",
"description": "El comportamiento ante rangos que superan el maximo permitido no esta definido por producto.",
"final_state": "escalated_open",
"verification": {
"against": "repository",
"detail": "El codigo no impone limite y el requerimiento no lo especifica: la decision no es tecnica."
}
},
{
"id": "h3",
"origin": "r1",
"severity": "minor",
"title": "Validacion de fechas supuestamente faltante",
"description": "El revisor reporto que la exportacion no valida el rango de fechas de entrada.",
"final_state": "refuted_verifiable",
"verification": {
"against": "repository",
"detail": "La validacion existe en el objeto de consulta compartido que este flujo reutiliza."
},
"evidence": {
"text": "El objeto de consulta compartido valida el rango antes de ejecutar; la exportacion lo reutiliza sin excepcion.",
"link": "src/consultas/RangoFechas.cs#L41-L58"
}
}
],
"residue": {
"items": [
{
"id": "r1",
"class": "escalation_without_decision",
"finding_ref": "h2",
"requires_human_attention": true,
"description": "El limite de filas de la exportacion quedo escalado a producto y sin resolver al cierre del ciclo."
},
{
"id": "r2",
"class": "principal_refutation",
"finding_ref": "h3",
"refutation_type": "verifiable",
"requires_human_attention": false,
"description": "Refutacion con enlace al objeto de consulta compartido, para que el revisor humano pueda no darla por buena.",
"evidence": {
"text": "La validacion vive en el objeto de consulta compartido reutilizado por la exportacion.",
"link": "src/consultas/RangoFechas.cs#L41-L58"
}
},
{
"id": "r3",
"class": "execution_gap",
"requires_human_attention": true,
"gap_reason": "environment_not_reproducible",
"description": "El comportamiento bajo concurrencia real no se probo porque el entorno de desarrollo no la reproduce."
}
]
},
"metrics": {
"counts": {
"total_findings": 3,
"valid": {
"incorporated": 1,
"debt_recorded": 0,
"owner_decision": 0
},
"false_positives": {
"refuted_verifiable": 1,
"refuted_interpretive": 0
},
"escalated_open": 1
},
"extra_time_sec": 2400
}
}04
Qué hace cumplir el gate
Una GitHub Action que valida las declaraciones que el PR agrega, aplica la política de alcance del repositorio y publica el resultado como comentario.
- Falla cerrado
- Si el gate no puede resolver el rango del PR, no da verde. Un control de cumplimiento que no puede decidir, no aprueba.
- La evidencia es de solo agregar
- Un PR no puede modificar, borrar ni renombrar declaraciones que ya estaban, ni reutilizar un identificador de evento.
- Una declaración rancia no cubre nada
- Si la ruta cambió después del commit revisado, la declaración deja de cubrirla. Revisar y seguir escribiendo no alcanza.
- Todo sale de git
- El alcance se deriva de los objetos del rango revisado, nunca del working tree: leer del disco clasificaría un árbol y validaría otro.
05
El límite, dicho por adelantado
El validador detecta el campo vacío y el marcador genérico. No detecta la declaración falsa, y ninguna herramienta puede. El muestreo humano de PR cerrados sigue siendo la única defensa real contra el cumplimiento cosmético.
Tampoco protege contra un workflow modificado, salteado o sustituido: eso lo resuelve la plataforma, con required checks estrictos, CODEOWNERS y el pin de la Action por SHA.
Probalo sin tocar tu CI
El gate corre igual en tu máquina y dice exactamente lo mismo que diría en CI. Recién cuando quieras que haga cumplir, se escribe el workflow.
pip install disensor && disensor init --no-workflow