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.

GrupoQué ofreceQué se exige para cambiarlo
coreAPI congelada para v1.0: lo que un tercero puede compilar sin romperse en un minorADR en docs/adr/ + revisión en PR
experimentalNada: la API puede cambiar entre commits menoresjust 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.

  1. Tests en verde de sus caminos principales, e imprescindiblemente: - concurrencia: si lanza tareas, cada tokio::spawn tiene camino de cancelación; - apagado: si abre sockets, hay test de que se cierran; - error: los caminos de fallo devuelven error tipado, no panic!.
  2. Auditoría de hardware real. Medido con hecate-node profile en al menos un nodo físico, con la procedencia de cada campo declarada en docs/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 de debug.
  3. 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:

SitioQué se comprueba
Cargo.tomles la fuente; se lee con tomllib
pyproject.tomldynamic = ["version"] y el patrón captura la versión correcta
CHANGELOG.mdexiste la sección ## [<versión>]
CITATION.cffversion: 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-proto no depende de ningún otro crate del workspace: es el contrato.
  • mesh-worker-net no conoce el planificador ni el orquestador: mueve bytes.
  • mesh-profiler no abre sockets de nodos ajenos: mide el nodo local.
  • Ningún crate de core depende de uno de experimental. 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).