Source document
SCM y modularización del workspace
Spanish text, transcribed from the repository. The unformatted
source is
scm.md.
Este documento fija dónde vive cada crate, qué garantía ofrece cada uno y cómo se asciende un crate de experimental a core. Es la referencia cuando alguien se pregunta «¿puedo cambiar esta API?».
Estructura del repositorio
HECATE/
├── Cargo.toml # workspace: members, versión única, lints
├── justfile # superficie de tareas (just check / audit / build)
├── pyproject.toml # paquete Python; la versión viene de Cargo.toml
├── llms.txt # descripción canónica para LLMs
├── .well-known/ai.txt # manifiesto legible por máquina
├── CITATION.cff # metadatos de citación
├── CHANGELOG.md # Keep a Changelog
├── CONTRIBUTING.md # ramas, commits y flujo de PR
├── proto/hecate.proto # contrato único (DataPlane + Telomerase)
├── crates/
│ ├── core/ # API congelada para v1.0
│ │ ├── hecate-node/ # binario CLI (serve, plan, zoo, guard, bench…)
│ │ ├── mesh-proto/ # tipos generados del .proto
│ │ ├── mesh-discovery/ # mDNS, registro de nodos
│ │ ├── mesh-profiler/ # perfilado real del nodo
│ │ ├── mesh-router/ # enrutamiento y failover
│ │ ├── mesh-scheduler/ # reparto de capas, model zoo, selector
│ │ ├── mesh-worker-net/ # framing, pool, clientes gRPC
│ │ └── mesh-orchestrator/ # clúster, crecimiento, host_guard
│ └── experimental/ # API inestable (hoy vacío, ver su README)
├── python/hecate/ # plano de datos (PyTorch, tokenizador, LoRA)
├── tests/
│ ├── rust/ # integración de los dos planos
│ └── python/
├── scripts/ # verificación y herramientas (sin CI externa)
└── docs/ # arquitectura, SCM, auditorías, benchmarks
Por qué core y experimental
El repositorio tiene nueve paquetes y un solo mantenedor. Sin una separación explícita, la pregunta «¿puedo cambiar esta firma?» no tiene respuesta, y en la práctica se acaba tratando todo como estable, lo que congela también lo que aún se está tanteando.
La separación resuelve eso de la forma más barata posible: la ubicación es el estado. No hay una lista de «crates estables» en un documento que se pueda desincronizar del árbol; hay dos directorios.
| Grupo | Qué ofrece | Qué se exige para cambiarlo |
|---|---|---|
core | API congelada para v1.0: lo que un tercero puede compilar sin romperse en un minor | ADR en docs/adr/ + revisión en PR |
experimental | Nada: la API puede cambiar entre commits menores | just check en verde; misma calidad, distinta promesa |
Importante: experimental no significa descuidado. Un crate de experimental pasa exactamente los mismos just check, scripts/verify.sh y lints que uno de core. La diferencia es la promesa de compatibilidad, no el nivel de exigencia.
Promoción de experimental a core
Los tres criterios son acumulativos.
- Tests en verde de sus caminos principales, e imprescindiblemente: - concurrencia: si lanza tareas, cada
tokio::spawntiene camino de cancelación; - apagado: si abre sockets, hay test de que se cierran; - error: los caminos de fallo devuelven error tipado, nopanic!. - Auditoría de hardware real. Medido con
hecate-node profileen al menos un nodo físico, con la procedencia de cada campo declarada endocs/benchmarks/hardware-real.md. Un crate ejercitado sólo contra mocks no se promociona: el criterio existe porque el perfilador ya se equivocó una vez clasificando un nodo a partir de números dedebug. - ADR aprobado en
docs/adr/que fije la API pública (tipos, traits, errores) y justifique por qué merece la garantía de compatibilidad.
El trámite es mecánico:
git mv crates/experimental/<crate> crates/core/<crate>
# actualizar `members` en el Cargo.toml del workspace
just check
No hay que tocar ninguna lista más: si algo funcionaba por la ruta antigua, es que estaba mal referenciado y el compilador lo dirá.
Versionado
La versión se declara una sola vez, en Cargo.toml:
[workspace.package]
version = "0.6.0"
Los ocho crates la heredan con version.workspace = true. pyproject.toml no tiene versión literal: la lee del mismo fichero con la fuente regex de hatchling.
[project]
dynamic = ["version"]
[tool.hatch.version]
source = "regex"
path = "Cargo.toml"
pattern = '(?m)^version = "(?P<version>[^"]+)"'
Por qué no se usa maturin
La técnica habitual para tener una sola versión entre Rust y Python es version-source de maturin. Aquí no aplica: maturin empaqueta extensiones de Rust que se importan desde Python, y HECATE no tiene ninguna. El plano de control es un binario independiente y el plano de datos es Python puro; la frontera entre ambos es gRPC, no PyO3. Por eso el backend es hatchling ([build-system] requires = ["hatchling"]) y el equivalente a version-source es su fuente regex, que apunta al mismo Cargo.toml.
Consecuencia práctica: si algún día se expone Rust a Python con PyO3, el backend pasa a maturin y esta sección cambia. Mientras no haya PyO3, añadir maturin sólo añadiría un requisito de compilación que hoy no hace falta.
Deriva de versión
scripts/check_versions.py comprueba los cuatro sitios que deben contar la misma historia:
| Sitio | Qué se comprueba |
|---|---|
Cargo.toml | es la fuente; se lee con tomllib |
pyproject.toml | dynamic = ["version"] y el patrón captura la versión correcta |
CHANGELOG.md | existe la sección ## [<versión>] |
CITATION.cff | version: coincide |
Se ejecuta en just audit y en scripts/verify.sh, así que una versión a medias falla antes de salir del equipo.
Etiquetas
Cada release lleva una etiqueta anotada, no ligera:
git tag -a v0.6.0 -m "v0.6.0 — consolidación: SCM, modularización y visibilidad"
git push origin main --tags # publica commits y etiquetas en la organización
Una etiqueta ligera es sólo un puntero; la anotada guarda quién, cuándo y por qué, que es lo que hace falta cuando dentro de un año se pregunta qué había en esa release.
Reglas de dependencia entre crates
El grafo es unidireccional y no tiene ciclos:
hecate-node → mesh-orchestrator → mesh-scheduler → mesh-profiler
↘ mesh-router → mesh-scheduler
↘ mesh-discovery
↘ mesh-worker-net → mesh-proto
Reglas que se comprueban en revisión:
mesh-protono depende de ningún otro crate del workspace: es el contrato.mesh-worker-netno conoce el planificador ni el orquestador: mueve bytes.mesh-profilerno abre sockets de nodos ajenos: mide el nodo local.- Ningún crate de
coredepende de uno deexperimental. Si lo necesita, la dependencia está al revés y hay que promover el experimental primero.
Superficie de tareas
justfile es el único punto de entrada. scripts/verify.sh sigue siendo el guardián que ejecuta el hook pre-push, y just audit lo invoca. Las dos superficies no se solapan por capricho: just es para humanos (nombres cortos, valores por defecto), verify.sh es para el hook (sin dependencias, sin just instalado, ejecutable en una máquina recién clonada).