Esta release sale de instanciar la plantilla en un proyecto Rust real y encontrar, tirando del mismo hilo tres veces, que tres campos del contrato declaraban una cobertura que no tenían. Los tres fallaban del mismo modo: verde silencioso, no error visible. Ninguno era detectable sin leer el código del gate.
| Qué se declaraba | Qué pasaba de verdad |
|---|---|
budget: max_cyclomatic_complexity: N |
El gate de Nivel 2 lee cyclomatic_max. El nombre que documentaba la plantilla nunca se leyó — 33/33 contratos del repo afectados. Un contrato con max_params: 1 y una firma de 5 parámetros pasaba sin un solo error. |
forbids: ['unsafe'] |
Nadie verificaba el contenido de forbids, solo que la lista no estuviera vacía. Se podía declarar sobre un proyecto Rust que permite unsafe sin que ningún gate lo notara. |
scan_secrets corriendo en CI, en verde |
Escaneaba solo ('.py','.js','.ts','.md','.json'): en un proyecto Rust/Go/Java miraba cero archivos y salía 0. La misma clave AWS daba ERROR en un .py y pasaba limpia en un .rs. |
El patrón, que es la lección
Un gate que no puede reportar su propia ausencia de cobertura no es un gate, es una sensación de cobertura. Por eso los tres arreglos no se limitan al fix: cada uno agrega un mecanismo para que la falta de cobertura se vea — FM_BUDGET_KEY, FORBID_UNVERIFIED, SECRETS_NO_FILES_SCANNED.
Nota metodológica: el agujero de scan_secrets estaba congelado por su propio oráculo — un test aseveraba literalmente que un .rs con una credencial devolvía []. tests_sha256 garantiza que el oráculo no cambió, no que sea correcto, y audit_seals detecta oráculos débiles, no equivocados.
Novedades
scripts/audit_forbids.py(nuevo, advisory opt-in): verificaforbidsdeclarado vs realmente impedido. Un verificador hoy —unsafeen Rust, el único donde la prohibición es comprobable de verdad porque rustc la impone sobre el crate entero.network/subprocess/llmse reportan comoFORBID_UNVERIFIED: siguen siendo declarativos y el auditor lo dice en vez de callar.scripts/scan_lint_suppressions.py(nuevo): detecta#[allow(clippy::…)]agregado en el diff para esquivar un lint en vez de arreglarlo.- Documentado el punto ciego de macros: la misma lógica mide
cyclomatic=5, nesting=4en código normal ycyclomatic=1, nesting=0dentro de unview! { }. Una función cuya lógica entera vive en un macro mide como si estuviera vacía. No es arreglable en un gate estático; se documenta el límite de la garantía. - Dónde van los tests de un proyecto no-Python, con el mecanismo concreto (
[[test]] pathen Rust) y sus dos consecuencias: hay que actualizar la clavetests:del contrato, y eltests_sha256no cambia (sella contenido, no ubicación).
Breaking para proyectos ya instanciados
- Los contratos con
max_cyclomatic_complexity/max_nesting_depthpasan a ERROR; el mensaje nombra el reemplazo canónico. Mismo patrón que fijótests_sha256. scan_secretsahora encuentra credenciales en archivos que antes ignoraba: un repo "en verde" puede pasar a rojo — y en ese caso el rojo es el correcto.
Verificación
validate_contracts 33 archivos, validate_okf 60 nodos, validate_changelog 33 contratos, lint_ascii 26 scripts — 0 errores. Suite completa 696 tests, 0 fallos. preflight.py 12/12.