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
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user