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é

MotivoDetalle
SoberaníaEl criterio de calidad es un fichero del repositorio, auditable y versionado.
Coste ceroSin minutos de CI, sin cuotas, sin runners que cambian de versión sin avisar.
IndependenciaMigrar el proyecto a otro forge no obliga a reescribir la verificación.
Reproducibilidadpyproject.toml fija ruff/mypy/pytest y Cargo.toml fija los lints de Rust.
Cobertura de la arquitectura realAl 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-prost 0.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 realQué se compruebaCómo se arregla
1python -m ruff check --no-fix .Lint de Python: imports muertos, comparaciones sospechosas, complejidad.python -m ruff check --fix .
2python -m ruff format --check .Formato de Python estable.python -m ruff format .
3python -m mypyTipado estricto de src/hecate.Corregir anotaciones; no añadir # type: ignore sin motivo.
4pytest --cov=hecate --cov-fail-under=70150 tests del plano de datos y telomerasa, cobertura mínima.Añadir tests; nunca bajar el umbral sin justificarlo.
5bash scripts/gen-proto.sh --checkLos stubs de Python coinciden con el .proto.bash scripts/gen-proto.sh
6emit_quant_fixture.py --checkEl fixture de cuantización sigue describiendo lo que hace el algoritmo.just fixture
7emit_acceleration_fixture.py --checkEl ratio de aceleración del ADR 0001 coincide en los dos planos.just fixture
8cargo fmt --all -- --checkFormato de Rust.cargo fmt --all
9cargo clippy --workspace --all-targets -- -D warningsLints del workspace con warnings como error.Corregir el aviso; no silenciarlo sin justificar el #[allow].
10cargo clippy -- -W clippy::pedanticSin avisos fuera de la lista justificada en Cargo.toml.Igual: corregir o justificar en Cargo.toml.
11cargo test --workspace437 tests de unidad, integración, propiedades y doctests.Corregir el test o el código, nunca ambos a la vez.
12cargo build --release -p hecate_nodeEl binario compila y enlaza con el linker de GNU.Revisar el toolchain (ver §2).
13hecate-node bench en debugEl binario de debug se niega a medir.Si mide, el guardián require_release se rompió.
14scripts/check_versions.pyLa versión es la misma en Cargo.toml, pyproject, CHANGELOG y CITATION.just versions dice cuál está atrás.
15scripts/check-scaffolding.shFicheros de política, visibilidad y recetas del justfile.Crear el fichero que falte.
16scripts/check-llms-txt.shllms.txt y .well-known/ai.txt válidos.Corregir la estructura, no el validador.
17scripts/check_docker_lab.pyEl 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].
18scripts/check_metrics.pyLas 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.
19scripts/check-css-classes.pyToda 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.
20scripts/check_hosting_links.py --offlineLos enlaces internos resuelven sin salir a Internet.Corregir la ruta del enlace.
21comprobación de docsREADME.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

VariablePor defectoUso
PYTHONautodetección (.venv, python3, python)Forzar un intérprete concreto.
CARGOcargo (con ~/.cargo/bin añadido al PATH)Forzar un cargo concreto.
COVERAGE_MIN70Umbral de cobertura exigido por pytest.
MIN_DOC_BYTES200Tamaño mínimo aceptado por documento.
SKIP_RUST01 omite los cinco pasos del plano de control y el chequeo de toolchain: quedan 17.
SKIP_PYTHON01 omite los siete pasos del plano de datos: quedan 15. Con los dos a 1, quedan 10.
VERIFY_HOOKvacíoSi 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 de cargo (compilación, clippy o test). El paso indica cuál.
  • 127/mensaje de intérprete: no se encontró Python o cargo; exporta PYTHON=... / 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:

  1. Localiza la raíz del repositorio.
  2. 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».
  3. Ejecuta scripts/tag-guard.sh: rechaza publicar una etiqueta vX.Y.Z que no apunte al commit publicado (o a un ancestro suyo, para permitir recolocar una etiqueta histórica).
  4. 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 diff que falle o una lista vacía dejan los dos planos encendidos.
  5. Verifica que scripts/verify.sh es ejecutable y lo ejecuta con SKIP_PYTHON, SKIP_RUST y VERIFY_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 enSe verifica
crates/, Cargo.toml, Cargo.lock, proto/Plano de control
python/, tests/python/, pyproject.toml, scripts/*.pyPlano de datos
scripts/, .github/, justfileLos 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íntomaCausa habitualSolución
no se encontró un intérprete de PythonNo hay .venv y python3 no está en PATH.Crear el venv o exportar PYTHON.
ModuleNotFoundError: ruffDependencias de desarrollo no instaladas.pip install -e ".[dev]".
no se encontró cargoToolchain Rust ausente o fuera del PATH.winget install Rustlang.Rustup; o SKIP_RUST=1.
error: linker 'x86_64-w64-mingw32-gcc' not foundFalta el linker de GNU en Windows.winget install BrechtSanders.WinLibs.POSIX.MSVCRT y reiniciar el shell.
error: package ID specification 'hecate-node' did not matchEl nombre del paquete usa guion bajo.Usar -p hecate_node (lo hace verify.sh).
clippy: error: unknown lint tras actualizar RustUn 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 usePuerto ocupado por otra ejecución.Los tests usan puertos efímeros (DEFAULT_BASE_PORT = 0); cerrar procesos colgados.
mypy: error: Library stubs not installedDependencia sin tipos.Cubierto por ignore_missing_imports; actualizar el paquete si persiste.
VERIFICACIÓN FALLIDA en el paso 10Documentación vacía o renombrada.Completar el documento; REQUIRED_DOCS está en scripts/verify.sh.
El hook no se ejecutacore.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 en verify.sh.
  • Seguridad de dependencias: ejecuta cargo audit / pip-audit aparte.
  • 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.