vet: veredicto sobre código generado por IA

codafort vet da un veredicto sobre lo que cambió desde HEAD o desde la base del PR: lo que bloquea, lo que es consultivo y lo que no corrió.

Evalúa solo el delta, en cinco ejes. Lo probado va a blocking y reprueba; el resto va a advisory. El veredicto dice qué ejes no corrieron, y un eje omitido nunca cuenta como limpio.

Uso

codafort vet                              # el delta vs HEAD (cambios sin commit)
codafort vet --all                        # el proyecto entero
codafort vet --json                       # el veredicto completo, coda-vet/1 (agentes/CI)
codafort vet --base origin/main           # en un PR: el delta es vs la BASE, no vs HEAD
codafort vet --ingest tsc.out             # correlaciona lo que el CI ya corrió (tsc/junit-xml/lcov)
codafort vet --evidence-out vet.json      # escribe coda-evidence/1 (modality: vet) para la declaración contrafirmada

Exit codes: 0 pasa · 1 veredicto bloqueante · 2 error de ejecución. Con el 2, una falla de ejecución nunca se lee como bloqueo.

Los ejes

EjePregunta¿Bloquea?
V0-security-delta¿Hay un finding de seguridad nuevo en el cambio? (el mismo análisis de scan, solo en el diff)confirmado sí; sospechoso es consultivo
V1-ingested-diagnostics¿El tsc/junit/lcov que el CI ya corrió tiene error en línea nueva?error sí; warning aconseja
V2-vacuous-tests¿Prueba que pasa sin probar (assert True, sin aserción, mock que devuelve lo esperado)?consultivo
V3-public-contract¿Símbolo público eliminado/renombrado (contract_breaks)?sí
V4-blast-radiusAlcance: archivos, líneas, archivos críticos tocados, wideconsultivo; alimenta prioridad
healthComplejidad ciclomática/cognitiva de las funciones tocadasnunca

V1 solo corre con --ingest; V3/V4 necesitan un delta. Cuando no corren, aparecen en axes.skipped, no en ran.

codafort no ejecuta tsc, pytest ni otra herramienta: lee el artefacto que el CI ya produjo y lo cruza con el diff.

La forma del veredicto (coda-vet/1)

{
  "schema": "coda-vet/1",
  "blocked": true,
  "axes": { "ran": ["V0-security-delta", "V2-vacuous-tests", "V3-public-contract", "V4-blast-radius"],
            "skipped": ["V1-ingested-diagnostics"] },
  "blocking": [ { "id": "SF-1", "severity": "Critical", "tier": "confirmed", "rule": "TAINT-COMMAND-INJECTION",
                  "cwe": [78], "file": "./app.py", "line": 7, "is_new": true,
                  "taint": { "source": "os.environ", "sink": "os.system" } } ],
  "advisory": [],
  "ingested": [],
  "contract_breaks": [ { "file": "./app.py", "symbol": "antiga_api", "kind": "function" } ],
  "scope": { "files_changed": 1, "lines_added": 4, "critical_files": [], "wide": false },
  "diff_only": true
}

Contrato publicado en /schemas; un veredicto real capturado en /schemas/fixtures/coda-vet.json.

En el CI (GitHub Action)

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }          # la BASE debe existir localmente
- uses: codafort/gate-action@v1
  with:
    source: '.'
    vet: 'true'                     # default
    vet-base: 'origin/main'         # vacío = base del PR (github.base_ref)
    vet-ingest: 'tsc.out'           # opcional: artefacto ya producido por el CI
    vet-enforce: 'true'             # un veredicto BLOQUEANTE reprueba el job

En un checkout superficial el step intenta git fetch de la base. Si aún no resuelve, vet se omite con ::warning (report-only) o reprueba (vet-enforce: true); nunca pasa en silencio sin haber corrido. Detalles en CI y el gate.

Del veredicto a la declaración contrafirmada

--evidence-out vet.json escribe la contribución coda-evidence/1 con modality: "vet": blocking, advisory, scope, axes_ran, axes_skipped, verdict_digest. Adjunta con codafort attest create --evidence vet.json, entra al ledger que codafort contrafirma. Ver La cadena de evidencia y Declaración contrafirmada (el artefacto coda-attestation/1).