Files
takana/docs/06-swm-format.md
T
sergioandClaude Opus 4.8 e867bac388 Etapa F paquetería #2: repositorio + hammer install <nombre> / repo list
Cierra el lazo "packié un .swm → lo instalo por nombre". El repo es el namespace que
le da identidad a los .swm (que en sí no la llevan).

- hammer-core/repo.rs: `RepoIndex` + `PackageEntry` (load/save index.json, find, upsert
  idempotente por nombre que reporta el .swm huérfano). Índice JSON plano, ordenado,
  diffeable, firmable a futuro como release. 4 tests.
- hammer-cli:
  * `pack --repo DIR` PUBLICA (escribe <repo>/<name>-<version>.swm + upsert al índice con
    distro_version/expected_hash/signed_by; retira el huérfano de una versión vieja).
  * `install <nombre> [--repo] [--trust] [--base-ref] [--prefix] [--skip-source-patch]`
    CONSUME: resuelve nombre→.swm, verifica firma (con --trust) ANTES de reproducir, delega
    en el camino de apply (reproduce source_patch + hidrata). Nunca corre binario ajeno.
    Nombre inexistente → error legible con los disponibles.
  * `repo list` imprime el catálogo.
- Validado E2E en host: publicar ripgrep (firmado) + findutils, repo list, index.json limpio,
  install ripgrep --trust → "firma: trusted (by alice)" → apply OK. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:28:16 -04:00

7.7 KiB

SDD 06 — Formato .swm (Software Mutación)

El .swm es la unidad de intercambio de hammer. No es un paquete binario (.deb, .rpm, ni un PKGBUILD que compila quién-sabe-qué). Es un manifiesto de la mutación: la receta de transformación sobre código fuente público, más las ediciones de configuración. El receptor reproduce y verifica localmente; nunca ejecuta tu binario (ver SDD 09).

1. Esquema

# my-grep-tweak.swm
swm_version: 1

base:
  distro_version: "2026-06-06"      # versión del set base de hammer
  pins:                              # commits fijados de los upstreams relevantes
    kernel: "a1b2c3d…"
    musl:   "e4f5a6b…"
  # base_hash deriva de pins+distro_version; el receptor verifica compatibilidad

mutations:
  - type: source_patch              # recompilar una herramienta desde su fuente parcheada
    repo: "git://git.savannah.gnu.org/grep.git"
    commit: "a1b2c3d…"              # commit fijado sobre el que aplica el patch
    patch: |                         # parche .patch de git, embebido o por URL (patch_url)
      --- a/src/main.c
      +++ b/src/main.c
      @@ ...
    build:
      compiler: "zig-cc"
      target: "x86_64-linux-musl"
      link: "static"
      flags: ["--enable-perl-regexp"]
    target_bin: "/bin/grep"

  - type: config_edit               # editar un archivo de config en texto plano
    file: "/etc/network.conf"
    inline_diff: |
      - DHCP=yes
      + IP=192.168.1.100

  - type: init_rule                 # añadir/ajustar una regla del init (ver SDD 07)
    action: "watchdog"
    service: "web"
    command: "/bin/myweb -p 8080"

signature:                           # opcional pero recomendado (ver SDD 09)
  by: "sergio"
  alg: "ed25519"
  sig: "base64…"

2. Tipos de mutación

type Qué describe Cómo lo aplica el receptor
source_patch recompilar una herramienta desde fuente parcheada (git o tarball) clona repo@commit / baja tarball+sha256 → aplica patch → hammer build → hidrata en overlay
config_edit edición de un archivo de config aplica el inline_diff (3-way) sobre el archivo objetivo
init_rule regla de supervisión de un servicio materializa /etc/hammer/init.d/{service}.rule (TOML: service/action/command); disable/stop la retiran. El init (arje) lee ese árbol
file_drop depositar un archivo de datos no compilable escribe el archivo (con su hash declarado) en la ruta

file_drop admite dos modos para el contenido, mutuamente excluyentes:

  • content_b64 (RFC 4648, sin saltos): inline en el .swm. Útil para .swms autocontenidos (tests, snapshots offline, datos pequeños).
  • content_url: el receptor lo descarga. En ambos casos se verifica content_hash (BLAKE3) antes de escribir nada.

Cada source_patch es, en esencia, una Recipe (SDD 02) serializada para viajar. El build lleva el compilador por mutación (no global).

3. Aplicación (hammer apply)

hammer apply my-grep-tweak.swm
  1. Verifica la base. ¿Los pins/distro_version son compatibles con el sistema local? Si no, avisa qué difiere y se detiene (o --force bajo tu responsabilidad).
  2. Verifica la firma (si existe) contra las claves de confianza locales.
  3. Reproduce. Para cada source_patch: clona el repo al commit, aplica el patch, compila en el lab local → obtiene un artifact_hash.
  4. Aísla. Hidrata todo en un overlay temporal (SDD 04); aplica config_edit/init_rule/file_drop en esa capa. No toca el sistema base.
  5. Valida. Corres/pruebas en caliente. Si bien → hammer commit. Si mal → hammer discard.

Nunca se ejecuta un binario ajeno: sólo se transforma código fuente público con una receta que tu propio laboratorio compila.

4. Determinismo y verificación cruzada

Como el lab es determinista (SDD 02), el artifact_hash que produce tu máquina debe coincidir con el del autor (si declara expected_hash). Coincidencia ⇒ obtuviste exactamente el mismo binario, sin haberlo descargado. Discrepancia ⇒ algo difiere (toolchain, pin, patch) y hammer te dice qué. Esto es "verificar, no confiar" en la práctica.

5. No-objetivos del formato

  • No transporta binarios cocidos (salvo file_drop para datos no compilables, con hash).
  • No es Turing-completo: es declarativo para el intercambio, aunque el sistema que describe se viva imperativamente. (Lo declarativo aquí es honesto: describe una transformación, no aprisiona tu sistema.)
  • No asume una arquitectura: target viaja en cada source_patch.

6. Interfaz

// hammer-core
#[derive(Serialize, Deserialize)]
pub struct Swm { /* swm_version, base, mutations, signature */ }
impl Swm {
    pub fn from_yaml(s: &str) -> Result<Swm>;
    pub fn to_yaml(&self) -> Result<String>;
    pub fn verify_base(&self, local: &BaseRef) -> BaseCompat;
    pub fn verify_signature(&self, trust: &TrustStore) -> SigStatus;
}

CLI:

  • hammer apply <file.swm> — abre un overlay por defecto y aplica las mutaciones. Con --prefix DIR opera directamente bajo DIR sin overlay (tests, staging). Con --base-ref base.json aborta si la base local no es compatible.
  • hammer swm-verify <file.swm> — chequea schema, hashes de los file_drop inline y, con --base-ref, la compatibilidad de base.
  • hammer export --journal DIR > out.swm — lee el diario y emite un .swm con un file_drop por archivo modificado (estado final actual; los Delete se omiten). El receptor reproduce byte-a-byte. (Cuando el diario tiene provenance de receta, export emite source_patch en vez de file_drop — el mapa artefacto→receta ya existe.)
  • hammer pack <recipe.toml>dirección forward (Etapa F): empaqueta una receta del corpus como un .swm de un único source_patch. Vuelve una receta que el sistema ya sabe construir un paquete distribuible y reproducible-desde-fuente; es la inversa de apply. --target-bin fija el ancla de sanity (default /usr/bin/<name>); --expected b3:… o --build sellan el expected_hash (ancla "verificar, no confiar"); --sign KEY lo firma. El source_patch traslada con fidelidad build.{phases,zig_version,flags} y, en tarball, strip_components; los patches de la receta viajan inline (concatenados). Aún no modela deps (resolución entre paquetes = pieza posterior): si la receta declara build/runtime deps, pack lo advierte y el receptor debe tenerlas ya en su store para reproducir. Con --repo DIR publica en un repositorio (ver abajo) en vez de (o además de) --out.
  • hammer install <nombre> [--repo DIR]consume del repositorio: resuelve nombre en el índice, verifica la firma (con --trust DIR) y la base (con --base-ref), y delega en el camino de apply (reproduce el source_patch desde fuente + hidrata). Nunca corre un binario ajeno. --prefix/--skip-source-patch para staging y dry-run de schema.
  • hammer repo list [--repo DIR] — lista el catálogo (<repo>/index.json).

7. Repositorio de paquetes (Etapa F)

Un repo es un directorio con los .swm + un index.json que mapea nombre → paquete ({name, version, file, distro_version, expected_hash?, signed_by?}, orden alfabético, diffeable). Un .swm no lleva identidad propia (es un manifiesto de mutación, no "el paquete X"); la identidad la asigna el repo al publicar — el índice es el namespace. pack --repo publica con el nombre/versión de la receta (upsert idempotente por nombre; una versión nueva retira el .swm huérfano). El transporte del repo (filesystem / sshfs / mirror) es ortogonal: ver hammer-mirror para el CAS del store. Tipos en hammer-core::repo (RepoIndex, PackageEntry).