Files
takana/docs/06-swm-format.md
T
sergioandClaude Opus 4.8 339a7b07ef Etapa F paquetería #1: hammer pack — receta del corpus → paquete .swm (source_patch)
Cierra la dirección forward que faltaba (SDD 06 §6 la marcaba "para más adelante"):
una receta que el sistema ya sabe construir se vuelve un paquete distribuible y
reproducible-desde-fuente, inversa de `hammer apply`.

- hammer-core: `Swm::from_recipe(recipe, target_bin, patch_text, expected, distro)`
  (constructor puro: el caller lee los patches). `SwmBuild` gana `phases`+`zig_version`
  y `SourcePatch` gana `strip_components` (Option/skip ⇒ .swm viejos parsean igual) para
  reproducir con fidelidad el corpus real (22/34 recetas usan phases, 8 usan zig 0.13).
- hammer-build/swm_bridge: la dirección inversa (source_patch→Recipe→build) ahora traslada
  phases/zig_version/strip_components a la receta efímera ⇒ apply rehace idéntico.
- hammer-cli: `hammer pack <recipe> [--target-bin] [--out] [--expected|--build] [--sign]`.
  Concatena los patches inline; avisa si la receta declara deps (el source_patch aún no
  las modela = pieza posterior). `export` también enriquece su source_patch.
- Validado en host: ripgrep (git+patch+install custom), openssl (tarball+zig 0.13+phases),
  coreutils (multicall), findutils firmado → swm-verify "trusted". Tests core+bridge verde.

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

140 lines
6.5 KiB
Markdown

# 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](09-trust-model.md)).
## 1. Esquema
```yaml
# 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 `.swm`s
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](02-build-lab.md)) 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](04-overlay.md)); 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](02-build-lab.md)), 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
```rust
// 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.