# ADR 0019 — El dueño del formato Card: tawasuyu lo DEFINE, takana lo GENERA - **Estado:** PROPUESTO. La dirección se decide acá; **cuándo se paga la adopción** queda abierto y depende de una medición concreta (§Precondiciones, el hash de Stage 1). - **Fecha:** 2026-09-14 - **Frontera:** en takana, `crates/takana-bootstrap/src/lib.rs` (`STAGE1_SEED_CARD`, `SSHD_SERVICE_CARD`), `scripts/mudanza/formatos/arje.py`, `scripts/mudanza/formatos/{systemd,openrc}.py`, `scripts/mudanza/declarar.py`. En tawasuyu, `shared/card/card-core`, `03_ukupacha/arje/arje-card{,-builder}`, `03_ukupacha/arje/init/arje-absorb`. - **Relacionado:** [SDD 29](../29-mudanza.md) (la mudanza), [SDD 30](../30-servicios-de-paquete.md) (qué arranca), [ADR 0006](0006-pinned-commits.md) (dependencias por commit). ## Contexto La mudanza de gioser (SDD 29) produce tarjetas de arje: es como un servicio ajeno —systemd, OpenRC, o un proceso que corre y nadie declaró— se convierte en algo que `arje-zero` sabe levantar. El producto instalado también las emite: su `seed.card.json` y la card de `sshd` salen de `takana-bootstrap`. Nunca se escribió **quién es dueño de ese formato**, y por omisión hoy son dos repos. Esto ya se pagó un piso más abajo: dentro de takana había **dos emisores** del mismo formato (`declarar.py` y `formatos/arje.py`) y **cada uno tenía un campo distinto mal en la raíz** — uno mandaba `provides` vacío, el otro `supervision: Restart` sobre una card `Virtual`. Ninguno de los dos coincidía con la semilla real del producto. Se consolidó en un solo emisor. Este ADR es el mismo problema un piso más arriba: **el segundo emisor está en el otro repo**. ## Lo medido (2026-09-14) | qué | resultado | |---|---| | el tipo canónico existe, y está en tawasuyu | `shared/card/card-core::Card`. `arje-card` se describe a sí mismo: *«Alias histórico de card-core. Re-exporta tipos legacy (EntityCard ≡ Card)»* | | …y **valida** su versión | `pub const CARD_SCHEMA_VERSION: u16 = 1` + `if self.schema_version != CARD_SCHEMA_VERSION { … }` | | emisores de Card **dentro de takana** | **2**, en dos lenguajes: `takana-bootstrap` (JSON crudo en constantes `r#"…"#`) y `scripts/mudanza/formatos/arje.py` | | dependencias de `card-core` / `arje-card` en `crates/*/Cargo.toml` | **0** | | sitios donde takana escribe `"schema_version": 1` **a mano** | **5** | | `arje-absorb` (tawasuyu) | usa `card_core::Card` directamente y ya trae lectores `systemd.rs`, `openrc.rs`, `runit.rs`, `dinit.rs`, `sysvinit.rs` | | `scripts/mudanza/formatos/` (takana) | reimplementa en Python **dos de esos cinco** (`systemd.py`, `openrc.py`) y aporta uno que allá no existe: `proc.py` | | `card` como crate | **hoja**: sin deps internas, sólo crates.io ⇒ depender de él es barato | | `STAGE1_SEED_CARD` | **entra en `inputs` del hash** del bootstrap (`lib.rs:332`) | | el repo `card` | está entre los **26 que sólo existen en gioser**, la máquina que se borra | El dato que decide: **card-core valida la versión de esquema y takana la escribe a mano en cinco sitios.** El día que `CARD_SCHEMA_VERSION` pase a 2, arje rechazará las tarjetas de takana — y el fallo no aparece en un test: aparece **en el arranque de una caja recién instalada**, que es el peor sitio y el más tarde posible. Hoy coinciden por suerte, no por construcción. ## La pregunta que este ADR contesta «Integrar tawasuyu» admite dos lecturas, y la respuesta **no es la misma para las dos**: 1. **usar piezas de tawasuyu dentro de esta herramienta**, o 2. **que la herramienta viva en tawasuyu y desde acá se invoque**. > **Para el FORMATO, (1): se adopta el tipo de tawasuyu. > Para la HERRAMIENTA, ninguna de las dos: la mudanza es de takana y se queda acá.** La mudanza habla de recetas, perfiles, store, hidratación y ArtifactHash: su vocabulario es el de takana, y mudarla a tawasuyu la dejaría hablando de cosas que ese repo no tiene. Lo que cruza la frontera es **el tipo**, no la herramienta. ## Decisión **1. tawasuyu DEFINE el formato; takana lo GENERA.** `card-core` es la única definición. No se copia, no se reimplementa y no se documenta en paralelo. Una copia del tipo es la misma duplicación que este ADR cierra, sólo que con la palabra «vendor» delante. **2. Un solo emisor en takana, y que serialice con el TIPO, no con `format!`.** Un envoltorio fino (`takana-card`) sobre `card-core`, usado por `takana-bootstrap` **y** por la mudanza. El criterio de aceptación es negativo y comprobable: **cero literales `"schema_version"` en el árbol de takana**. **3. La mudanza no se muda.** Sigue en `scripts/mudanza/`, y su reescritura a subcomando (`takana migrate`) es una decisión de otro ADR — independiente de ésta. **4. Los lectores de init ajeno se retiran de takana, no se mejoran.** `arje-absorb` ya lee systemd/OpenRC/runit/dinit/sysvinit contra el tipo canónico; `formatos/systemd.py` y `openrc.py` son una segunda implementación de dos de ellos. Se retiran **cuando** absorb exponga su resultado de forma consumible por un tercero; hasta entonces conviven **con una prueba que compare las dos salidas sobre la misma entrada y falle si divergen** — porque dos traductores que nadie compara divergen sin que nada falle, que es exactamente cómo empezó esto. **5. Lo que takana aporta va HACIA tawasuyu, no en paralelo.** El lector `proc` —absorber del proceso VIVO y no de la declaración— es de takana y no existe allá. Y hace falta: en gioser `rc-status` decía `stopped` de cinco servicios **vivos**, así que absorb solo produciría una Semilla perfecta que no levanta el servidor. Se ofrece como aportación a `arje-absorb`; si tawasuyu no lo toma, se queda acá como lector propio **del tipo de allá**, que sigue siendo una sola definición. **6. La dirección de dependencia es única: takana → tawasuyu**, por commit pineado (ADR 0006). tawasuyu no depende de takana. No es una preferencia: **21 recetas del corpus ya construyen componentes de tawasuyu** (`arje-zero`, `mirada-*`, `netup`, `arje-absorb`, `arje-installer`, `cosmos-cli`, …). La dependencia inversa cerraría un ciclo entre dos repos que ya se necesitan en un sentido. ## Precondiciones que no se saltan **a. `card` tiene que tener copia fuera de gioser antes de que takana dependa de él.** Es uno de los 26 repos sin espejo. Hacer que el bootstrap de la distro dependa en tiempo de compilación de un repo que **sólo existe en la máquina que estamos por borrar** convierte un problema conocido en un bloqueo de arranque. Es el paso 1 del plan de mudanza, y es precondición de esta decisión. **b. Adoptar el tipo puede mover el baseline del selfhost, y hay que MEDIRLO antes.** `STAGE1_SEED_CARD` entra en los `inputs` del hash del bootstrap: si al serializar con `card-core` el JSON sale con otro orden de claves, otro espaciado u otro campo por defecto, **el hash de Stage 1 se mueve**. Se comprueba con `takana hash` antes y después; si se mueve, la adopción en `takana-bootstrap` es una decisión propia (con el selfhost delante) y **no se hace de paso**. La mudanza no tiene ese problema: sus cards no entran en ningún hash, así que puede adoptar el tipo primero. ## Consecuencias **Se gana:** cuando el esquema suba, el fallo pasa de *«una caja recién instalada no arranca»* a *«no compila»*. Es el mismo cambio de lugar que buscan la regla 3 del `CLAUDE.md` y el ADR 0012: que lo que se rompe, se rompa ruidosamente y temprano. **Cuesta:** takana suma una dependencia de compilación sobre un repo privado. Es barata en lo técnico (`card` es hoja, sólo crates.io) y cara en lo operativo mientras la precondición (a) siga sin cumplir. **Rechazado — copiar el tipo a takana (vendor):** quita la dependencia y deja exactamente el problema que este ADR existe para cerrar, con la divergencia además invisible. **Rechazado — mover la mudanza a tawasuyu:** el vocabulario de la mudanza es el de takana. ## Lo que NO decide este ADR - Dónde vive el `[[sitio]]` (la utilidad de vhosts y el versionado de datos por frentes). Es un ADR propio, y la respuesta puede ser distinta que ésta. - Si `arje-absorb` incorpora el lector `proc`: es decisión de tawasuyu, no de este repo. - Cuándo se paga la adopción en `takana-bootstrap` — depende de la medición (b). - Si la mudanza se reescribe como subcomando de takana.