Files
takana/docs/adr/0019-dueno-del-formato-card.md
SergioandClaude Opus 5 447e151424 ADR 0019: el dueño del formato Card — y takana lo escribe a mano en cinco sitios
La mudanza emite tarjetas de arje y el producto instalado también; quién es dueño del formato no
estaba escrito, así que por omisión son dos repos. Medido hoy:

  el tipo canónico existe y está en tawasuyu   `shared/card/card-core::Card`
  …y VALIDA su versión                          CARD_SCHEMA_VERSION = 1, comparada al deserializar
  emisores dentro de takana                     2, en dos lenguajes (bootstrap Rust + arje.py)
  deps de card-core en crates/*/Cargo.toml      0
  `"schema_version": 1` escrito a mano          5 sitios
  arje-absorb                                   usa card_core::Card y ya trae systemd/openrc/runit/
                                                dinit/sysvinit; formatos/ reimplementa DOS en Python

El día que el esquema pase a 2, arje rechaza las tarjetas de takana — y no falla en un test: falla
en el arranque de una caja recién instalada. Hoy coinciden por suerte, no por construcción. Es el
mismo fallo que ya se pagó un piso más abajo, cuando había dos emisores DENTRO de takana y cada uno
tenía un campo distinto mal en la raíz.

Decisión: tawasuyu DEFINE, takana GENERA. Un solo emisor acá y que serialice con el tipo, no con
`format!` — criterio de aceptación negativo: cero literales `"schema_version"` en el árbol. Los
lectores de init ajeno se RETIRAN (absorb ya los tiene), no se mejoran, y mientras convivan hay una
prueba que compara las dos salidas y falla si divergen. Lo que takana aporta —el lector `proc`, que
lee lo VIVO porque `rc-status` miente— va hacia absorb, no en paralelo.

Y la herramienta NO se muda a tawasuyu: la mudanza habla de recetas, perfiles, store y ArtifactHash.
Lo que cruza la frontera es el tipo, no la herramienta.

Dos precondiciones que no se saltan: `card` es uno de los 26 repos sin copia fuera de gioser (hacer
que el bootstrap dependa en compilación de un repo que sólo vive en la máquina que se borra es
convertir un problema conocido en un bloqueo de arranque); y `STAGE1_SEED_CARD` ENTRA EN EL HASH del
bootstrap, así que reserializar con card-core puede mover el baseline del selfhost — se mide con
`takana hash` antes, y si se mueve es una decisión propia. La mudanza no tiene ese problema y puede
adoptar el tipo primero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
2026-09-14 15:01:05 +00:00

8.2 KiB

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 (la mudanza), SDD 30 (qué arranca), ADR 0006 (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.