Source document
Verificación local
Spanish text, transcribed from the repository. The unformatted
source is
verify.md.
HECATE no usa CI en la nube. No hay .github/workflows/, ni CircleCI, ni GitLab CI. La calidad se garantiza en la máquina de quien escribe el código, con dos scripts del repositorio.
1. Por qué
| Motivo | Detalle |
|---|---|
| Soberanía | El criterio de calidad es un fichero del repositorio, auditable y versionado. |
| Coste cero | Sin minutos de CI, sin cuotas, sin runners que cambian de versión sin avisar. |
| Independencia | Migrar el proyecto a otro forge no obliga a reescribir la verificación. |
| Reproducibilidad | pyproject.toml fija ruff/mypy/pytest y Cargo.toml fija los lints de Rust. |
| Cobertura de la arquitectura real | Al ser un sistema híbrido, la verificación cubre los dos planos: si solo se verificara Python, el código más crítico (el bucle de red en Rust) quedaría sin red. |
La contrapartida aceptada: no hay una "insignia verde" pública generada por un servicio externo. La insignia del README es un SVG local (docs/badges/local-verify.svg) que documenta el modelo elegido, no un semáforo remoto.
2. Requisitos
Plano de datos (Python)
- Python 3.10 o superior.
- Dependencias de desarrollo:
python -m venv .venv
source .venv/bin/activate # Windows (PowerShell): .venv\Scripts\Activate.ps1
pip install -e ".[dev]"
[dev] instala ruff, mypy, pytest, pytest-cov y grpcio-tools.
Plano de control (Rust)
- Rust 1.88 o superior (edición 2024; lo exige
tonic-prost0.14).
# Windows: target GNU + linker de MinGW, sin Visual Studio
winget install Rustlang.Rustup
rustup default stable-x86_64-pc-windows-gnu
winget install BrechtSanders.WinLibs.POSIX.MSVCRT
# Linux / macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Si no tienes toolchain Rust, SKIP_RUST=1 ./scripts/verify.sh ejecuta solo el plano de datos (útil para editar documentación o el modelo). El hook pre-push no lo salta.
3. scripts/verify.sh
./scripts/verify.sh
Veintiuno pasos, en orden y con aborto al primer fallo:
| # | Comando real | Qué se comprueba | Cómo se arregla |
|---|---|---|---|
| 1 | python -m ruff check --no-fix . | Lint de Python: imports muertos, comparaciones sospechosas, complejidad. | python -m ruff check --fix . |
| 2 | python -m ruff format --check . | Formato de Python estable. | python -m ruff format . |
| 3 | python -m mypy | Tipado estricto de src/hecate. | Corregir anotaciones; no añadir # type: ignore sin motivo. |
| 4 | pytest --cov=hecate --cov-fail-under=70 | 150 tests del plano de datos y telomerasa, cobertura mínima. | Añadir tests; nunca bajar el umbral sin justificarlo. |
| 5 | bash scripts/gen-proto.sh --check | Los stubs de Python coinciden con el .proto. | bash scripts/gen-proto.sh |
| 6 | emit_quant_fixture.py --check | El fixture de cuantización sigue describiendo lo que hace el algoritmo. | just fixture |
| 7 | emit_acceleration_fixture.py --check | El ratio de aceleración del ADR 0001 coincide en los dos planos. | just fixture |
| 8 | cargo fmt --all -- --check | Formato de Rust. | cargo fmt --all |
| 9 | cargo clippy --workspace --all-targets -- -D warnings | Lints del workspace con warnings como error. | Corregir el aviso; no silenciarlo sin justificar el #[allow]. |
| 10 | cargo clippy -- -W clippy::pedantic | Sin avisos fuera de la lista justificada en Cargo.toml. | Igual: corregir o justificar en Cargo.toml. |
| 11 | cargo test --workspace | 437 tests de unidad, integración, propiedades y doctests. | Corregir el test o el código, nunca ambos a la vez. |
| 12 | cargo build --release -p hecate_node | El binario compila y enlaza con el linker de GNU. | Revisar el toolchain (ver §2). |
| 13 | hecate-node bench en debug | El binario de debug se niega a medir. | Si mide, el guardián require_release se rompió. |
| 14 | scripts/check_versions.py | La versión es la misma en Cargo.toml, pyproject, CHANGELOG y CITATION. | just versions dice cuál está atrás. |
| 15 | scripts/check-scaffolding.sh | Ficheros de política, visibilidad y recetas del justfile. | Crear el fichero que falte. |
| 16 | scripts/check-llms-txt.sh | llms.txt y .well-known/ai.txt válidos. | Corregir la estructura, no el validador. |
| 17 | scripts/check_docker_lab.py | El laboratorio Docker es coherente: sintaxis de los scripts, estructura del compose, servicios que los scripts esperan, puertos sólo en loopback y .dockerignore. No levanta contenedores. | Corregir el compose o el script; si falta PyYAML, instalar .[dev]. |
| 18 | scripts/check_metrics.py | Las cifras de README.md, llms.txt, .well-known/ai.txt y site/index.html coinciden con examples/metrics.json, y el propio JSON describe el árbol actual. | just sync-metrics recuenta y propaga; nunca editar las cifras a mano. |
| 19 | scripts/check-css-classes.py | Toda clase usada en el HTML del sitio tiene al menos una regla CSS. | Añadir la regla o quitar la clase del marcado, según cuál de las dos esté de más. |
| 20 | scripts/check_hosting_links.py --offline | Los enlaces internos resuelven sin salir a Internet. | Corregir la ruta del enlace. |
| 21 | comprobación de docs | README.md, docs/*.md, docs/adr/ y docker/ existen y superan MIN_DOC_BYTES. | Escribir documentación real, no esqueletos. |
Los lints de Rust no son opcionales: el workspace declara [workspace.lints.rust] unsafe_code = "forbid", missing_docs = "deny" y [workspace.lints.clippy] all = "deny". Un #[allow] sin justificación se detecta en revisión.
El paso 17 merece una aclaración: comprueba el laboratorio sin ejecutarlo, porque la verificación obligatoria tiene que funcionar en una máquina sin Docker. Que los contenedores arranquen y que los siete pasos del failover pasen no está verificado aquí, y así se declara en docs/benchmarks/docker-lab.md.
Salida esperada:
HECATE · verificación local
raíz: /ruta/a/HECATE
rama: main
commit: 1a2b3c4
python: .venv/bin/python (Python 3.14.6)
cargo: cargo (cargo 1.99.1 (5f94df478 2026-08-27))
[1/21] ruff check · lint del plano de datos
✔ sin avisos de lint
...
[6/21] paridad de cuantización · el fixture coincide con el algoritmo de Python
✔ el fixture de paridad de cuantización está al día
[7/21] paridad de aceleración · el fixture coincide con el ADR 0001
✔ el fixture de paridad de aceleración está al día
[8/21] cargo fmt --check · formato de Rust
✔ formato consistente
[9/21] cargo clippy · lints del workspace (warnings = error)
✔ clippy limpio
[10/21] cargo clippy::pedantic · sin avisos no justificados
✔ pedantic limpio
[11/21] cargo test · suite del plano de control (Rust + Tokio)
✔ tests de Rust en verde
[12/21] cargo build --release · binario hecate-node
✔ binario de release compilado y enlazado
[17/21] laboratorio Docker · coherencia estática (no se ejecuta)
✔ laboratorio Docker coherente
[18/21] métricas · la documentación coincide con el recuento
✔ cifras alineadas en README, llms.txt, ai.txt y el sitio
[19/21] CSS · toda clase del HTML tiene regla
✔ toda clase del HTML tiene regla CSS
[21/21] documentación · README y docs
TODO EN VERDE — plano de datos (Python) y plano de control (Rust) verificados
la rama puede publicarse (hook pre-push satisfecho)
Variables de entorno
| Variable | Por defecto | Uso |
|---|---|---|
PYTHON | autodetección (.venv, python3, python) | Forzar un intérprete concreto. |
CARGO | cargo (con ~/.cargo/bin añadido al PATH) | Forzar un cargo concreto. |
COVERAGE_MIN | 70 | Umbral de cobertura exigido por pytest. |
MIN_DOC_BYTES | 200 | Tamaño mínimo aceptado por documento. |
SKIP_RUST | 0 | 1 omite los cinco pasos del plano de control y el chequeo de toolchain: quedan 17. |
SKIP_PYTHON | 0 | 1 omite los siete pasos del plano de datos: quedan 15. Con los dos a 1, quedan 10. |
VERIFY_HOOK | vacío | Si está definida, imprime los milisegundos de cada paso. Es lo que permite responder «por qué tarda» con una columna en vez de con una suposición. |
Ejemplos:
COVERAGE_MIN=85 ./scripts/verify.sh
PYTHON=/usr/bin/python3.12 ./scripts/verify.sh
SKIP_RUST=1 ./scripts/verify.sh # solo plano de datos
SKIP_PYTHON=1 ./scripts/verify.sh # solo plano de control
SKIP_RUST=1 SKIP_PYTHON=1 ./scripts/verify.sh # solo SCM y documentación
Códigos de salida
0: todo en verde.1: algún paso ha fallado (el mensaje indica cuál:VERIFICACIÓN FALLIDA (código 1, paso 3/21)).101: fallo decargo(compilación, clippy o test). El paso indica cuál.127/mensaje de intérprete: no se encontró Python ocargo; exportaPYTHON=.../CARGO=....
4. scripts/install-hooks.sh
./scripts/install-hooks.sh # instala o reinstala el hook
./scripts/install-hooks.sh --force # sobrescribe un pre-push ajeno (guarda .bak)
./scripts/install-hooks.sh --remove # desinstala el hook
Escribe .git/hooks/pre-push (o core.hooksPath si está configurado) con este comportamiento:
- Localiza la raíz del repositorio.
- Guarda en un temporal las líneas que git pasa por stdin. Hacen falta dos consumidores —el guardián de etiquetas y el cálculo de qué planos se han tocado— y un flujo no se lee dos veces: leerlo mal deja al segundo con una entrada vacía que parece «no hay nada que verificar».
- Ejecuta
scripts/tag-guard.sh: rechaza publicar una etiquetavX.Y.Zque no apunte al commit publicado (o a un ancestro suyo, para permitir recolocar una etiqueta histórica). - Calcula qué planos ha tocado el push comparando la base con la punta, y apaga el que no ha cambiado. Es conservador a propósito: una referencia nueva, un borrado, un
git diffque falle o una lista vacía dejan los dos planos encendidos. - Verifica que
scripts/verify.shes ejecutable y lo ejecuta conSKIP_PYTHON,SKIP_RUSTyVERIFY_HOOK=1. Si falla, aborta el push.
4.bis Por qué el push de documentación no recompila Rust
Verificar los dos planos en cada push significa compilar el workspace para publicar un cambio de una coma en un .md. Eso paga dos veces por una garantía que ya se pagó: si el push no toca crates/, el árbol de Rust es el mismo que ya pasó estas puertas.
| Cambia algo en | Se verifica |
|---|---|
crates/, Cargo.toml, Cargo.lock, proto/ | Plano de control |
python/, tests/python/, pyproject.toml, scripts/*.py | Plano de datos |
scripts/, .github/, justfile | Los dos |
docs/, README.md, site/ | Ninguno: quedan SCM y documentación |
La fila de scripts/ no es un descuido: el instrumento que mide no se valida a sí mismo. Si cambia verify.sh, count-tests.sh o un guarda, la medición se repite entera con el instrumento nuevo.
Medido: un push que sólo toca documentación y scripts/ pasó de ~10 min a 10,1 s cuando se saltaron los dos planos. Un push que toca scripts/ sigue tardando lo que tiene que tardar (~3 min), porque ahí sí hay que remirarlo todo.
Si algún día hace falta publicar sin verificar (caso de fuerza mayor), existe git push --no-verify; el README y este documento recomiendan no usarlo.
Windows
Los hooks de git se ejecutan con el intérprete de shell disponible. En Windows funciona con Git Bash (incluido con Git for Windows):
& "C:\Program Files\Git\bin\bash.exe" .\scripts\install-hooks.sh
& "C:\Program Files\Git\bin\bash.exe" .\scripts\verify.sh
verify.sh añade al PATH tanto ~/.cargo/bin como el mingw64/bin de WinLibs si los encuentra, así que funciona lanzado desde un shell no interactivo (que es como git ejecuta los hooks).
Si tu consola no tiene bash, ejecuta los mismos comandos de la tabla del apartado 3 a mano: son exactamente los que llama verify.sh.
5. Refuerzo opcional con pre-commit
.pre-commit-config.yaml declara un único hook local que invoca scripts/verify.sh en la fase pre-push. No descarga nada de la red ni depende de servicios externos.
pip install pre-commit
pre-commit install --hook-type pre-push
Es una alternativa, no un sustituto: si no tienes pre-commit, el hook de install-hooks.sh hace lo mismo con cero dependencias.
6. Flujo recomendado antes de publicar
python -m ruff check --fix . # arregla lo automático
python -m ruff format . # formatea Python
cargo fmt --all # formatea Rust
./scripts/verify.sh # verifica los dos planos
git add -A
git commit -m "feat: ..."
git push # el hook vuelve a verificar (red de seguridad)
7. Resolución de problemas
| Síntoma | Causa habitual | Solución |
|---|---|---|
no se encontró un intérprete de Python | No hay .venv y python3 no está en PATH. | Crear el venv o exportar PYTHON. |
ModuleNotFoundError: ruff | Dependencias de desarrollo no instaladas. | pip install -e ".[dev]". |
no se encontró cargo | Toolchain Rust ausente o fuera del PATH. | winget install Rustlang.Rustup; o SKIP_RUST=1. |
error: linker 'x86_64-w64-mingw32-gcc' not found | Falta el linker de GNU en Windows. | winget install BrechtSanders.WinLibs.POSIX.MSVCRT y reiniciar el shell. |
error: package ID specification 'hecate-node' did not match | El nombre del paquete usa guion bajo. | Usar -p hecate_node (lo hace verify.sh). |
clippy: error: unknown lint tras actualizar Rust | Un lint nuevo se activó con -D warnings. | Corregir el aviso; el código generado por tonic ya está aislado en mesh_proto::v1. |
los stubs generados no coinciden (paso 5) | Se editó el .proto sin regenerar. | bash scripts/gen-proto.sh y commit de los *_pb2*.py. |
Tests de red fallan con OSError: address already in use | Puerto ocupado por otra ejecución. | Los tests usan puertos efímeros (DEFAULT_BASE_PORT = 0); cerrar procesos colgados. |
mypy: error: Library stubs not installed | Dependencia sin tipos. | Cubierto por ignore_missing_imports; actualizar el paquete si persiste. |
VERIFICACIÓN FALLIDA en el paso 10 | Documentación vacía o renombrada. | Completar el documento; REQUIRED_DOCS está en scripts/verify.sh. |
| El hook no se ejecuta | core.hooksPath apunta a otro sitio. | git config --get core.hooksPath; reinstalar con HECATE_HOOKS_DIR=.... |
8. Qué NO se verifica aquí
- Rendimiento: la latencia real depende del hardware; se mide con
docs/benchmarks/methodology.md, no enverify.sh. - Seguridad de dependencias: ejecuta
cargo audit/pip-auditaparte. - Interoperabilidad entre máquinas: los tests cubren el protocolo en loopback; una prueba entre dos equipos es manual (y recomendable antes de una release).
- Calidad del modelo: los fragmentos se inicializan con una semilla. La verificación comprueba que las matemáticas y el contrato son correctos, no que el modelo sea bueno.