gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1534Shell 199Rust 109Markdown 993713 Definitions

⎇ 551a103e2dd119ec47b28d831074a214c8a080d2 docs/06-swm-format.md Markdown · 200 lines · 11.9 KB History raw

repo list/verify: hablan HTTP — un repo remoto con 173 paquetes se leía como vacío e0e390c28 · Sergio ·

1# SDD 06 — Formato `.swm` (Software Mutación)
2
3El `.swm` es la unidad de intercambio de `takana`. **No es un paquete binario** (`.deb`,
4`.rpm`, ni un `PKGBUILD` que compila quién-sabe-qué). Es un **manifiesto de la mutación**: la
5receta de transformación sobre código fuente público, más las ediciones de configuración. El
6receptor reproduce y verifica localmente; **nunca ejecuta tu binario** (ver
7[SDD 09](09-trust-model.md)).
8
9## 1. Esquema
10
11```yaml
12# my-grep-tweak.swm
13swm_version: 1
14
15base:
16  distro_version: "2026-06-06"      # versión del set base de takana
17  pins:                              # commits fijados de los upstreams relevantes
18    kernel: "a1b2c3d…"
19    musl:   "e4f5a6b…"
20  # base_hash deriva de pins+distro_version; el receptor verifica compatibilidad
21
22mutations:
23  - type: source_patch              # recompilar una herramienta desde su fuente parcheada
24    repo: "git://git.savannah.gnu.org/grep.git"
25    commit: "a1b2c3d…"              # commit fijado sobre el que aplica el patch
26    patch: |                         # parche .patch de git, embebido o por URL (patch_url)
27      --- a/src/main.c
28      +++ b/src/main.c
29      @@ ...
30    build:
31      compiler: "zig-cc"
32      target: "x86_64-linux-musl"
33      link: "static"
34      flags: ["--enable-perl-regexp"]
35    target_bin: "/bin/grep"
36
37  - type: config_edit               # editar un archivo de config en texto plano
38    file: "/etc/network.conf"
39    inline_diff: |
40      - DHCP=yes
41      + IP=192.168.1.100
42
43  - type: init_rule                 # añadir/ajustar una regla del init (ver SDD 07)
44    action: "watchdog"
45    service: "web"
46    command: "/bin/myweb -p 8080"
47
48signature:                           # opcional pero recomendado (ver SDD 09)
49  by: "sergio"
50  alg: "ed25519"
51  sig: "base64…"
52```
53
54## 2. Tipos de mutación
55
56| `type` | Qué describe | Cómo lo aplica el receptor |
57|---|---|---|
58| `source_patch` | recompilar una herramienta desde fuente parcheada (git **o** tarball) | clona repo@commit / baja tarball+sha256 → aplica patch → `takana build` → hidrata en overlay |
59| `config_edit` | edición de un archivo de config | aplica el `inline_diff` (3-way) sobre el archivo objetivo |
60| `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» era FALSO** — verificado el 2026-09-12: el único uso de `INIT_RULES_DIR` en todo el repo es ESCRIBIRLO, y el contrato real de arje son `/ente/seed.card.json` y `/etc/arje/cards.d/*.json`. Esta mutación es hoy **write-only**: derogarla o reapuntarla a `cards.d/` es [SDD 30 §3.2](30-servicios-de-paquete.md) |
61| `file_drop` | depositar un archivo de datos no compilable | escribe el archivo (con su hash declarado) en la ruta |
62
63`file_drop` admite dos modos para el contenido, mutuamente excluyentes:
64
65- `content_b64` (RFC 4648, sin saltos): inline en el `.swm`. Útil para `.swm`s
66  autocontenidos (tests, snapshots offline, datos pequeños).
67- `content_url`: el receptor lo descarga. En ambos casos se verifica `content_hash`
68  (BLAKE3) antes de escribir nada.
69
70Cada `source_patch` es, en esencia, una `Recipe` ([SDD 02](02-build-lab.md)) serializada para
71viajar. El `build` lleva el **compilador por mutación** (no global).
72
73## 3. Aplicación (`takana apply`)
74
75```
76takana apply my-grep-tweak.swm
77```
78
791. **Verifica la base.** ¿Los `pins`/`distro_version` son compatibles con el sistema local?
80   Si no, avisa qué difiere y se detiene (o `--force` bajo tu responsabilidad).
812. **Verifica la firma** (si existe) contra las claves de confianza locales.
823. **Reproduce.** Para cada `source_patch`: clona el repo al commit, aplica el patch, compila
83   en el lab local → obtiene un `artifact_hash`.
844. **Aísla.** Hidrata todo en un **overlay temporal** ([SDD 04](04-overlay.md)); aplica
85   `config_edit`/`init_rule`/`file_drop` en esa capa. **No toca el sistema base.**
865. **Valida.** Corres/pruebas en caliente. Si bien → `takana commit`. Si mal → `takana discard`.
87
88Nunca se ejecuta un binario ajeno: sólo se transforma código fuente público con una receta que
89tu propio laboratorio compila.
90
91## 4. Determinismo y verificación cruzada
92
93Como el lab es determinista ([SDD 02](02-build-lab.md)), el `artifact_hash` que produce tu
94máquina debe coincidir con el del autor (si declara `expected_hash`). Coincidencia ⇒ obtuviste
95exactamente el mismo binario, sin haberlo descargado. Discrepancia ⇒ algo difiere (toolchain,
96pin, patch) y `takana` te dice qué. Esto es "verificar, no confiar" en la práctica.
97
98## 5. No-objetivos del formato
99
100- **No** transporta binarios cocidos (salvo `file_drop` para datos no compilables, con hash).
101- **No** es Turing-completo: es declarativo *para el intercambio*, aunque el sistema que
102  describe se viva imperativamente. (Lo declarativo aquí es honesto: describe una
103  transformación, no aprisiona tu sistema.)
104- **No** asume una arquitectura: `target` viaja en cada `source_patch`.
105
106## 6. Interfaz
107
108```rust
109// takana-core
110#[derive(Serialize, Deserialize)]
111pub struct Swm { /* swm_version, base, mutations, signature */ }
112impl Swm {
113    pub fn from_yaml(s: &str) -> Result<Swm>;
114    pub fn to_yaml(&self) -> Result<String>;
115    pub fn verify_base(&self, local: &BaseRef) -> BaseCompat;
116    pub fn verify_signature(&self, trust: &TrustStore) -> SigStatus;
117}
118```
119
120CLI:
121
122- `takana apply <file.swm>` — abre un overlay por defecto y aplica las mutaciones. Con
123  `--prefix DIR` opera directamente bajo `DIR` sin overlay (tests, staging). Con
124  `--base-ref base.json` aborta si la base local no es compatible.
125- `takana swm-verify <file.swm>` — chequea schema, hashes de los `file_drop` inline y,
126  con `--base-ref`, la compatibilidad de base.
127- `takana export --journal DIR > out.swm` — lee el diario y emite un `.swm` con un
128  `file_drop` por archivo modificado (estado final actual; los `Delete` se omiten). El
129  receptor reproduce byte-a-byte. (Cuando el diario tiene provenance de receta, `export`
130  emite `source_patch` en vez de `file_drop` — el mapa artefacto→receta ya existe.)
131- `takana pack <recipe.toml>` — **dirección forward (Etapa F):** empaqueta una receta del
132  corpus como un `.swm` de un único `source_patch`. Vuelve una receta que el sistema ya sabe
133  construir un paquete distribuible y reproducible-desde-fuente; es la inversa de `apply`.
134  `--target-bin` fija el ancla de sanity (default `/usr/bin/<name>`); `--expected b3:…` o
135  `--build` sellan el `expected_hash` (ancla "verificar, no confiar"); `--sign KEY` lo firma.
136  El `source_patch` traslada con fidelidad `build.{phases,zig_version,flags}`, en tarball
137  `strip_components`, y las `deps` por nombre; los patches de la receta viajan inline
138  (concatenados). Si la receta declara build-deps, `pack` recuerda publicarlas en el mismo repo.
139  Con `--repo DIR` **publica** en un repositorio (ver abajo) en vez de (o además de) `--out`.
140- `takana install <nombre> [--repo DIR|URL]` — **consume** del repositorio: resuelve `nombre` en
141  el índice, verifica la firma del release y la del `.swm` (con `--trust DIR`) y la base (con
142  `--base-ref`), **resuelve el cierre transitivo de build-deps** desde el índice (orden
143  topológico) poblando un catálogo de recetas, y delega en el camino de `apply` (reproduce el
144  `source_patch` desde fuente, con sus deps materializadas en el sandbox, + hidrata). Nunca corre
145  un binario ajeno. Registra el paquete en la DB de instalados (`--db`). `--repo` admite un
146  directorio local **o una URL HTTP(S)** (el repo son ficheros estáticos: `index.json` + los
147  `.swm`; con URL baja el índice + el cierre de deps a un temporal y procede igual).
148  `--prefix`/`--skip-source-patch` para staging y dry-run de schema. `--require-signed` exige que
149  el release esté firmado por una clave confiada (modo estricto: no basta con reproducir).
150- `takana repo list [--repo DIR|URL]` — lista el catálogo (`<repo>/index.json`). Desde el
151  2026-09-21 acepta lo MISMO que `install --repo`: un directorio o una lista de orígenes HTTP(S)
152  separada por comas. Antes sólo miraba directorios y ante una URL respondía **«vacío» con salida
153  0** — un repo remoto con 173 paquetes se leía como uno sin nada. Y ahora distingue «todavía no
154  hay `index.json`» (repo por crear) de «índice publicado y sin paquetes», que también se daban
155  por iguales.
156- `takana repo sign --repo DIR --key KEY` — firma el ÍNDICE entero (release). Re-firmá tras
157  publicar (cada `pack --repo` invalida la firma del release). **Sólo local:** firmar es escribir.
158- `takana repo verify --repo DIR|URL [--trust DIR]` — verifica la firma del release. También
159  acepta orígenes HTTP(S): verificar el release de un espejo ANTES de instarle nada es justo el
160  caso de uso del ADR 0014, y hasta el 2026-09-21 no había forma de hacerlo sin construir.
161- `takana uninstall <nombre> [--db FILE]` — borra los ficheros que el paquete registró (refcount:
162  respeta los que otro paquete instalado también aporta) y lo quita de la DB de instalados.
163- `takana installed [--db FILE]` — lista los paquetes instalados. `install` registra cada paquete
164  (nombre, versión, hash, ficheros creados) en la DB (`/var/lib/hammer/installed.json` por defecto).
165- `takana import-nix [FILE]` — **Etapa G (poblar el catálogo desde nixpkgs):** toma el JSON
166  normalizado de un paquete nix (lo produce `scripts/nix-import.sh <attr>` vía `nix eval`) y emite
167  una receta takana. Trae la RECETA (source+hash+deps+flags), NUNCA el binario del cache de nix —
168  takana reconstruye desde fuente igual ("verificar, no confiar"). `fetchurl` plano → `tarball`
169  +sha256 (hash nix→hex); GitHub → `repo`+`commit`. Es un PUNTO DE PARTIDA: el build en el lab de
170  takana (zig/musl) suele necesitar adaptación por paquete. Pipeline: `nix-import.sh hello` →
171  receta → `pack` → `.swm` → repo → `install` (reproduce desde fuente). Es cómo el catálogo crece
172  de 34 recetas a-mano a miles sin reescribirlas.
173
174## 7. Repositorio de paquetes (Etapa F)
175
176Un **repo** es un directorio con los `.swm` + un `index.json` que mapea `nombre → paquete`
177(`{name, version, file, distro_version, expected_hash?, signed_by?}`, orden alfabético,
178diffeable). Un `.swm` no lleva identidad propia (es un manifiesto de mutación, no "el paquete
179X"); la identidad la asigna el repo al publicar — el índice es el namespace. `pack --repo`
180publica con el nombre/versión de la receta (upsert idempotente por nombre; una versión nueva
181retira el `.swm` huérfano). El repo son ficheros estáticos, así que `install --repo URL` lo
182consume por HTTP(S) (cualquier servidor estático sirve; baja `index.json` + el cierre de deps a
183un temporal). Tipos en `takana-core::repo` (`RepoIndex`, `PackageEntry`).
184
185**Dependencias.** Un `source_patch` lleva sus build-deps por NOMBRE (las de la receta original);
186la `PackageEntry` las espeja para resolver el grafo sin abrir cada `.swm`. `install <nombre>`
187calcula el cierre transitivo (`RepoIndex::resolve_closure`, topológico, error si falta una dep o
188hay ciclo) y, para reproducir, **reconstruye un catálogo de recetas** efímero: escribe un
189`{dep}.toml` por dep (sintetizado de su `.swm` vía `swm_bridge::recipe_from_source_patch`) en el
190`catalog_dir` determinista que el lab consulta al materializar build-deps en el sandbox. Así un
191paquete con deps (p. ej. `bwrap`→`libcap`, `openssh`→`zlib,openssl`) se reproduce desde fuente
192bit-idéntico — verificado: `install bwrap` reproduce el artefacto cacheado exacto resolviendo
193`libcap` del catálogo.
194
195**Firma del release.** Además de la firma de cada `.swm` (autoría del paquete), el ÍNDICE entero
196se puede firmar como release (`RepoIndex::signature`, Ed25519 sobre la lista de paquetes
197canónica). Ancla qué paquetes/versiones/hashes existen: un atacante no puede añadir, quitar ni
198intercambiar entradas sin invalidar la firma. Cualquier `upsert` (publicar) la invalida ⇒
199re-firmar tras publicar. `install` verifica el release antes de resolver: una firma mala aborta
200("el índice fue manipulado"). Mismo primitivo Ed25519 que el `.swm` (`sign::{sign_raw,verify_raw}`).

06-swm-format.md

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

El .swm es la unidad de intercambio de takana. 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 takana
  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

typeQué describeCómo lo aplica el receptor
source_patchrecompilar una herramienta desde fuente parcheada (git o tarball)clona repo@commit / baja tarball+sha256 → aplica patch → takana build → hidrata en overlay
config_editedición de un archivo de configaplica el inline_diff (3-way) sobre el archivo objetivo
init_ruleregla de supervisión de un serviciomaterializa /etc/hammer/init.d/{service}.rule (TOML: service/action/command); disable/stop la retiran. ⚠ «El init (arje) lee ese árbol» era FALSO — verificado el 2026-09-12: el único uso de INIT_RULES_DIR en todo el repo es ESCRIBIRLO, y el contrato real de arje son /ente/seed.card.json y /etc/arje/cards.d/*.json. Esta mutación es hoy write-only: derogarla o reapuntarla a cards.d/ es SDD 30 §3.2
file_dropdepositar un archivo de datos no compilableescribe el archivo (con su hash declarado) en la ruta

file_drop admite dos modos para el contenido, mutuamente excluyentes:

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 (takana apply)

takana 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 → takana commit. Si mal → takana 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 takana te dice qué. Esto es "verificar, no confiar" en la práctica.

5. No-objetivos del formato

6. Interfaz

// takana-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:

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 repo son ficheros estáticos, así que install --repo URL lo consume por HTTP(S) (cualquier servidor estático sirve; baja index.json + el cierre de deps a un temporal). Tipos en takana-core::repo (RepoIndex, PackageEntry).

Dependencias. Un source_patch lleva sus build-deps por NOMBRE (las de la receta original); la PackageEntry las espeja para resolver el grafo sin abrir cada .swm. install <nombre> calcula el cierre transitivo (RepoIndex::resolve_closure, topológico, error si falta una dep o hay ciclo) y, para reproducir, reconstruye un catálogo de recetas efímero: escribe un {dep}.toml por dep (sintetizado de su .swm vía swm_bridge::recipe_from_source_patch) en el catalog_dir determinista que el lab consulta al materializar build-deps en el sandbox. Así un paquete con deps (p. ej. bwrap→libcap, openssh→zlib,openssl) se reproduce desde fuente bit-idéntico — verificado: install bwrap reproduce el artefacto cacheado exacto resolviendo libcap del catálogo.

Firma del release. Además de la firma de cada .swm (autoría del paquete), el ÍNDICE entero se puede firmar como release (RepoIndex::signature, Ed25519 sobre la lista de paquetes canónica). Ancla qué paquetes/versiones/hashes existen: un atacante no puede añadir, quitar ni intercambiar entradas sin invalidar la firma. Cualquier upsert (publicar) la invalida ⇒ re-firmar tras publicar. install verifica el release antes de resolver: una firma mala aborta ("el índice fue manipulado"). Mismo primitivo Ed25519 que el .swm (sign::{sign_raw,verify_raw}).