1# ADR 0014 — Distribución multi-origen: el origen no necesita confianza
2
3- **Estado:** ACEPTADO — implementado en `crates/hammer-build/src/fetch.rs`,
4 `crates/hammer-core/src/repo.rs` y `crates/hammer-cli/src/main.rs` (`RepoSource`).
5- **Fecha:** 2026-09-01
6- **Frontera:** `bases_de_mirror` / `rotar_bases`, `PackageEntry::digest`, `repo::verify_digest`,
7 `RepoSource::{parse,read_index,materialize}`.
8- **Continúa:** [ADR 0013](0013-mirror-de-fuentes.md) (mirror de fuentes) y
9 [SDD 19 §3](../19-lanzamiento-publico.md) (infraestructura pública).
10
11## Contexto
12
13**Todavía no hay usuarios.** Ése es exactamente el momento de decidir esto: el protocolo de
14distribución toca el formato del índice y el comportamiento de clientes ya instalados en máquinas
15ajenas. Cambiarlo después de la primera descarga pública significa romper a alguien o arrastrar dos
16formatos para siempre; cambiarlo hoy no cuesta nada.
17
18La pregunta que lo dispara es la correcta: *no quiero apoyarme en un solo servidorcito.* Y hoy nos
19apoyamos exactamente en eso, en tres capas a la vez:
20
21| capa | qué sirve | orígenes soportados **antes** de este ADR |
22|---|---|---|
23| fuentes tarball | `{sha256}.tar` | **1** (`TAKANA_MIRROR`) |
24| fuentes git | `{commit}.bundle` | **1** (`TAKANA_MIRROR_GIT`) |
25| repo de paquetes | `index.json` + `.swm` | **1** (`--repo <URL>`) |
26
27El ADR 0013 existe precisamente para no depender de 79 servidores ajenos — y lo resolvió creando una
28dependencia de **uno nuestro**. Es el mismo punto único de fallo con nuestro nombre encima.
29
30### La medida: qué habría que servir, y cuánto pesa
31
32| qué | tamaño | naturaleza |
33|---|---:|---|
34| tarballs de fuentes (`work/tarballs`, 393 objetos) | 2,9 G | inmutable, direccionado por `sha256` |
35| bundles git de fuentes (571 commits) | ~2,3 G | inmutable, direccionado por `commit` |
36| artefactos / paquetes | 36 G en el hub; **~51 G** el espejo publicable tras el split de debug ([SDD 23](../23-plan-rehasheo.md)) | inmutable, direccionado por BLAKE3 |
37| imágenes de instalación | unos pocos G por release | inmutable |
38| **índice firmado del repo** | **KB** | **mutable, y lo único que debe ser fresco y auténtico** |
39
40Es decir: **el 99,99% de los bytes es inmutable y verificable por contenido, y el 0,01% restante es
41lo único que necesita confianza.** Esa asimetría es la decisión entera.
42
43## Decisión
44
45### 1. La partición: raíz chica y firmada, cuerpo grande y sin confianza
46
47- **Raíz** — el `index.json` firmado. Debe ser fresca y auténtica. Es de kilobytes, así que puede
48 vivir en sitios que no escalan pero sí se controlan: el propio git (que ya se espeja a gitea **y**
49 a GitHub, ver `scripts/espejo-setup.sh`), o cualquier host.
50- **Cuerpo** — tarballs, bundles, `.swm`, artefactos, imágenes. Decenas de gigas, y **cero
51 necesidad de confianza**: el nombre del objeto ES su verificación, o su hash está anclado en la
52 raíz firmada.
53
54⇒ El cuerpo puede servirse desde **cualquier host, incluso uno hostil**. Eso convierte «conseguir
55espejos» de un problema de infraestructura y de contratos a un problema de a quién pedírselo.
56
57### 2. Todo camino de descarga acepta una LISTA de orígenes, no uno
58
59`TAKANA_MIRROR` y `TAKANA_MIRROR_GIT` pasan a ser listas separadas por comas. `--repo` acepta
60`https://a,https://b`. Añadir orígenes **no re-hashea nada**: la URL nunca estuvo en `hash_inputs`
61(ADR 0013 §1), y para el repo el `digest` no es un input de build sino un campo del índice.
62
63### 3. Las fuentes ROTAN el orden; el repo lo respeta
64
65Distinto a propósito, y la razón importa:
66
67- **Fuentes** — es la granja moliendo el corpus sin nadie mirando, cientos de objetos por tanda. Si
68 se probara siempre en orden, el segundo origen no se tocaría jamás hasta la emergencia: sería *un
69 espejo que nadie prueba*, la lección literal del ADR 0013 §2 un nivel más arriba. Se rota con una
70 semilla **determinista tomada del propio hash del objeto**: el mismo objeto sale siempre del mismo
71 origen (un fallo se reproduce y se depura), pero objetos distintos se reparten entre todos, así
72 que **una tanda del corpus ejercita la lista entera**. Tras el elegido se recorren los demás.
73- **Repo** — es una persona instalando un paquete. El orden es una preferencia suya (el espejo más
74 cercano primero) y la previsibilidad vale más que el reparto; además son un puñado de ficheros, no
75 una tanda. Se prueban en el orden que escribió.
76
77### 4. Ausencia ⇒ seguir. Contenido distinto ⇒ ABORTAR
78
79La distinción que hace que una lista de orígenes sea una mejora y no un agujero:
80
81- Un origen que **no tiene** el objeto (404, host caído, DNS) es normal: se pasa al siguiente en
82 silencio. Un espejo puede ir por detrás.
83- Un origen que devuelve **otro contenido** del que la raíz firmada declara es manipulación o
84 corrupción. **Se aborta ahí mismo**, no se cae al siguiente origen.
85
86Caer al siguiente sería la tentación natural («total, del bueno lo bajo igual») y es justo el patrón
87que este repo ya pagó caro: *un ausente falla ruidosamente; un vacío llega hasta el final diciendo
88que todo fue bien* (CLAUDE.md §3). El paquete acabaría instalado desde el espejo bueno y **nadie se
89enteraría nunca de que uno de los espejos miente**.
90
91### 5. El índice firmado ancla el `digest` de cada `.swm` — el hueco que había
92
93Antes de este ADR la cadena estaba **rota en el último eslabón**, y no era evidente. La firma del
94índice cubre la lista de entradas: nombre, versión, `file`, deps. Pero `file` es una **ruta, no un
95contenido**: quien sirviera los bytes podía devolver otro `.swm` bajo el mismo nombre y la firma del
96índice seguía casando perfectamente.
97
98Había red aguas abajo — `apply` reconstruye desde fuente y compara contra `expected_hash` — pero no
99alcanza: `expected_hash` es **opcional**, y el `.swm` se lee mucho antes de llegar ahí (el gate de
100colisiones de ficheros de `install` ya decide con su contenido). El comentario de `download.rs`
101decía que el `patch_url` «se cubre indirectamente»; *indirectamente* no es una cadena de confianza.
102
103Se cierra con `PackageEntry::digest` (BLAKE3, `b3:…`), sellado al publicar y verificado antes de
104escribir un byte. La cadena queda: **clave raíz → firma del índice → `digest` → bytes**.
105
106> **CORRECCIÓN (mismo día).** Este párrafo decía que el `digest` usaba «la misma función que sella
107> artefactos», o sea `of_inputs(&[bytes])` — length-prefijado. **Estaba mal, y la razón importa.**
108> Con una sola entrada el length-prefijado no desambigua ninguna concatenación: sólo hace que
109> `b3:<hex>` **deje de ser el hash que obtiene un tercero** con `b3sum` sobre el mismo archivo.
110>
111> Lo destapó escribir la contraparte de este ADR para churay (`02_ruway/churay/SDD-DISTRIBUCION.md`
112> §5 en tawasuyu): churay direcciona sus blobs con `blake3(bytes)` pelado y takana iba a hacerlo
113> length-prefijado, **bajo el mismo prefijo `b3:`** ⇒ el mismo archivo con dos hex distintos, y un
114> CAS compartido que no falla ruidosamente sino que busca nombres distintos para los mismos bytes.
115>
116> Ahora usa `ArtifactHash::of_bytes` — que **ya existía en takana** y ya era blake3 pelado; es la
117> convención de `of_file` y la del `expected_hash` de un `.swm`, así que `of_inputs` era además la
118> pieza fuera de sitio *dentro* del propio repo. Coste del cambio: una línea, porque ningún índice
119> publicado llevaba todavía el campo. Es el argumento de este ADR aplicado a sí mismo — decidir
120> antes de que haya usuarios. Hay guardián: `digest_es_blake3_pelado_y_no_length_prefijado`.
121
122El campo es `Option` con `skip_serializing_if`, así que **un índice firmado antes de este cambio
123serializa idéntico y su firma sigue siendo válida** (hay test). Un índice sin digests no bloquea,
124pero **dice cuántos paquetes no verificó**: un índice viejo servido desde un espejo ajeno no es
125seguro, y el silencio lo haría parecer verificado.
126
127### 6. Cloudflare R2 como primer origen público — instancia, no arquitectura
128
129El criterio no es el precio del almacenamiento (51 G no cuestan nada en ningún sitio): es el
130**egress**, que es lo que quiebra a los proyectos que reparten binarios. R2 no cobra salida.
131
132Órdenes de magnitud, **a verificar el día de contratar** — no son cotizaciones:
133
134| origen | almacenamiento | egress |
135|---|---|---|
136| Storage Box BX11 (ya pagado) | €3,20/mes por 1 TiB | sin cargo, pero no es un CDN |
137| Cloudflare R2 | ~US$0,015/GB-mes ⇒ **<US$1/mes por 51 G** | **0** |
138| Backblaze B2 + Cloudflare | ~US$6/TB-mes | 0 vía Bandwidth Alliance |
139| Hetzner Object Storage | ~€6/mes (1 TB + 1 TB) | incluido hasta el TB |
140
141**Lo que decide este ADR no es «usamos R2»: es que R2 sea una entrada más de una lista.** Si mañana
142cierra la cuenta, se añade otra base a la lista y no cambia una línea de código ni se re-hashea un
143artefacto. La condición de no depender de un servidorcito no se cumple contratando un proveedor
144grande — se cumple pudiendo perder cualquiera de ellos sin enterarse.
145
146Regla que se deriva: **nunca menos de dos orígenes en dos proveedores distintos**. Hoy el repo, el
147respaldo y el mirror de fuentes están los tres en Hetzner y en la misma cuenta — riesgo que el ADR
1480013 ya dejó anotado sin resolver, y que R2 resuelve por ser de otra casa.
149
150### 7. ⛔ Lo que NUNCA sale hacia el CDN
151
152La **clave raíz de firma**, que es offline y es lo único no delegable de todo el lanzamiento
153([SDD 19 §3.2](../19-lanzamiento-publico.md), [SDD 20](../20-catalogo-publicable-y-completa.md)). Un
154CDN sirve bytes públicos; firmar es lo contrario de publicar. Si la clave estuviera en el sitio desde
155donde se sirve, los N espejos dejarían de ser «orígenes sin confianza» y pasarían a ser N copias de
156la autoridad.
157
158## Verificado de punta a punta, no por inspección
159
160Dos servidores HTTP locales con el **mismo `index.json`** (mismo nombre de paquete, mismo `file`,
161mismo `digest`) y `.swm` distintos: el bueno y una copia con **un solo byte cambiado** — el mismo
162tamaño, el mismo nombre de fichero. Es exactamente lo que puede hacer un espejo que no controlamos.
163
164| escenario | resultado | qué prueba |
165|---|---|---|
166| 1. sólo el origen bueno | pasa el digest, baja, carga el `.swm`, y muere después en el `config_edit` (el paquete sintético toca un `/etc/network.conf` que no existe) | la descarga y la verificación se cruzan de verdad |
167| 2. sólo el origen manipulado | `Error: digest de 'demo' no coincide … el origen entregó un .swm distinto del que se firmó`, exit 1 | el byte cambiado se detecta |
168| 3. origen **muerto** primero (:9999), bueno detrás | llega al mismo punto que (1), y reporta `bajados 1 .swm de http://127.0.0.1:8801` | **la lista hace failover ante ausencia** |
169| 4. origen **manipulado** primero, bueno detrás | aborta con el mismo error que (2), exit 1 | **NO cae al siguiente ante contenido distinto** — §4 |
170
171(3) y (4) son el par que importa: sin el segundo, la lista sería un mecanismo para tapar espejos
172mentirosos en vez de para sobrevivir a espejos caídos.
173
174Además, el mensaje de (3) nombra **el origen que sirvió de verdad**, no la lista entera. Decir
175«bajado de \<lista\>» cuando sólo uno respondió es una media verdad que esconde justo el dato con el
176que se depura un espejo caído — el mismo error de diagnóstico que el ADR 0013 documenta en el
177poblador de bundles.
178
179## Consecuencias
180
181- **Cero re-hasheos.** La URL no está en `hash_inputs` y `digest` no es un input de build.
182- **Cero roturas de firmas existentes.** Verificado por test: sin `digest`, `PackageEntry` serializa
183 idéntico a antes.
184- Un espejo nuevo pasa a ser un `rsync` plano sin índice, sin coordinación y sin confianza. La
185 barrera de entrada para que un tercero nos espeje es más baja que la de una distro clásica, y eso
186 es una consecuencia directa del direccionamiento por contenido, no un favor que pedimos.
187- Aparece deuda de vigilancia (ver abajo): más orígenes = más sitios que pueden pudrirse en silencio.
188
189## Lo que este ADR NO decide
190
191- **El vigía de los espejos.** `scripts/fuentes/fuentes-vigia.sh` vigila **upstream**, no nuestros
192 orígenes. Con una lista de N, hace falta comprobar que **cada uno** tiene lo que dice tener — si no,
193 volvemos a la métrica que envejece hacia el optimismo. Es el trabajo pendiente más claro que deja
194 este documento.
195- **Qué origen es la verdad al publicar** (cómo se propaga un release de uno a los demás, y qué pasa
196 si la propagación se corta a media tanda: un espejo con índice nuevo y `.swm` viejo).
197- **Las imágenes de instalación**: son ficheros grandes de un solo tiro y el reparto natural es
198 torrent con webseed, que no comparte mecánica con esto.
199- **La rotación de claves de firma** y el procedimiento ante filtración — sigue siendo de
200 [SDD 19 §3.2](../19-lanzamiento-publico.md), y sin él la firma sigue siendo teatro por muchos
201 orígenes que haya.
202- **La concentración en `github.com`** (64% del corpus como fuente). Este ADR no la toca; sigue
203 pendiente desde el 0013.
0014-distribucion-multiorigen.md
ADR 0014 — Distribución multi-origen: el origen no necesita confianza
- Estado: ACEPTADO — implementado en
crates/hammer-build/src/fetch.rs,
crates/hammer-core/src/repo.rs y crates/hammer-cli/src/main.rs (RepoSource).
- Fecha: 2026-09-01
- Frontera:
bases_de_mirror / rotar_bases, PackageEntry::digest, repo::verify_digest,
RepoSource::{parse,read_index,materialize}.
- Continúa: ADR 0013 (mirror de fuentes) y
SDD 19 §3 (infraestructura pública).
Contexto
Todavía no hay usuarios. Ése es exactamente el momento de decidir esto: el protocolo de
distribución toca el formato del índice y el comportamiento de clientes ya instalados en máquinas
ajenas. Cambiarlo después de la primera descarga pública significa romper a alguien o arrastrar dos
formatos para siempre; cambiarlo hoy no cuesta nada.
La pregunta que lo dispara es la correcta: no quiero apoyarme en un solo servidorcito. Y hoy nos
apoyamos exactamente en eso, en tres capas a la vez:
| capa | qué sirve | orígenes soportados antes de este ADR |
| fuentes tarball | {sha256}.tar | 1 (TAKANA_MIRROR) |
| fuentes git | {commit}.bundle | 1 (TAKANA_MIRROR_GIT) |
| repo de paquetes | index.json + .swm | 1 (--repo <URL>) |
El ADR 0013 existe precisamente para no depender de 79 servidores ajenos — y lo resolvió creando una
dependencia de uno nuestro. Es el mismo punto único de fallo con nuestro nombre encima.
La medida: qué habría que servir, y cuánto pesa
| qué | tamaño | naturaleza |
tarballs de fuentes (work/tarballs, 393 objetos) | 2,9 G | inmutable, direccionado por sha256 |
| bundles git de fuentes (571 commits) | ~2,3 G | inmutable, direccionado por commit |
| artefactos / paquetes | 36 G en el hub; ~51 G el espejo publicable tras el split de debug (SDD 23) | inmutable, direccionado por BLAKE3 |
| imágenes de instalación | unos pocos G por release | inmutable |
| índice firmado del repo | KB | mutable, y lo único que debe ser fresco y auténtico |
Es decir: el 99,99% de los bytes es inmutable y verificable por contenido, y el 0,01% restante es
lo único que necesita confianza. Esa asimetría es la decisión entera.
Decisión
1. La partición: raíz chica y firmada, cuerpo grande y sin confianza
- Raíz — el
index.json firmado. Debe ser fresca y auténtica. Es de kilobytes, así que puede
vivir en sitios que no escalan pero sí se controlan: el propio git (que ya se espeja a gitea y
a GitHub, ver scripts/espejo-setup.sh), o cualquier host.
- Cuerpo — tarballs, bundles,
.swm, artefactos, imágenes. Decenas de gigas, y cero
necesidad de confianza: el nombre del objeto ES su verificación, o su hash está anclado en la
raíz firmada.
⇒ El cuerpo puede servirse desde cualquier host, incluso uno hostil. Eso convierte «conseguir
espejos» de un problema de infraestructura y de contratos a un problema de a quién pedírselo.
2. Todo camino de descarga acepta una LISTA de orígenes, no uno
TAKANA_MIRROR y TAKANA_MIRROR_GIT pasan a ser listas separadas por comas. --repo acepta
https://a,https://b. Añadir orígenes no re-hashea nada: la URL nunca estuvo en hash_inputs
(ADR 0013 §1), y para el repo el digest no es un input de build sino un campo del índice.
3. Las fuentes ROTAN el orden; el repo lo respeta
Distinto a propósito, y la razón importa:
- Fuentes — es la granja moliendo el corpus sin nadie mirando, cientos de objetos por tanda. Si
se probara siempre en orden, el segundo origen no se tocaría jamás hasta la emergencia: sería un
espejo que nadie prueba, la lección literal del ADR 0013 §2 un nivel más arriba. Se rota con una
semilla determinista tomada del propio hash del objeto: el mismo objeto sale siempre del mismo
origen (un fallo se reproduce y se depura), pero objetos distintos se reparten entre todos, así
que una tanda del corpus ejercita la lista entera. Tras el elegido se recorren los demás.
- Repo — es una persona instalando un paquete. El orden es una preferencia suya (el espejo más
cercano primero) y la previsibilidad vale más que el reparto; además son un puñado de ficheros, no
una tanda. Se prueban en el orden que escribió.
4. Ausencia ⇒ seguir. Contenido distinto ⇒ ABORTAR
La distinción que hace que una lista de orígenes sea una mejora y no un agujero:
- Un origen que no tiene el objeto (404, host caído, DNS) es normal: se pasa al siguiente en
silencio. Un espejo puede ir por detrás.
- Un origen que devuelve otro contenido del que la raíz firmada declara es manipulación o
corrupción. Se aborta ahí mismo, no se cae al siguiente origen.
Caer al siguiente sería la tentación natural («total, del bueno lo bajo igual») y es justo el patrón
que este repo ya pagó caro: un ausente falla ruidosamente; un vacío llega hasta el final diciendo
que todo fue bien (CLAUDE.md §3). El paquete acabaría instalado desde el espejo bueno y nadie se
enteraría nunca de que uno de los espejos miente.
5. El índice firmado ancla el digest de cada .swm — el hueco que había
Antes de este ADR la cadena estaba rota en el último eslabón, y no era evidente. La firma del
índice cubre la lista de entradas: nombre, versión, file, deps. Pero file es una ruta, no un
contenido: quien sirviera los bytes podía devolver otro .swm bajo el mismo nombre y la firma del
índice seguía casando perfectamente.
Había red aguas abajo — apply reconstruye desde fuente y compara contra expected_hash — pero no
alcanza: expected_hash es opcional, y el .swm se lee mucho antes de llegar ahí (el gate de
colisiones de ficheros de install ya decide con su contenido). El comentario de download.rs
decía que el patch_url «se cubre indirectamente»; indirectamente no es una cadena de confianza.
Se cierra con PackageEntry::digest (BLAKE3, b3:…), sellado al publicar y verificado antes de
escribir un byte. La cadena queda: clave raíz → firma del índice → digest → bytes.
CORRECCIÓN (mismo día). Este párrafo decía que el digest usaba «la misma función que sella
artefactos», o sea of_inputs(&[bytes]) — length-prefijado. Estaba mal, y la razón importa.
Con una sola entrada el length-prefijado no desambigua ninguna concatenación: sólo hace que
b3:<hex> deje de ser el hash que obtiene un tercero con b3sum sobre el mismo archivo.
Lo destapó escribir la contraparte de este ADR para churay (02_ruway/churay/SDD-DISTRIBUCION.md
§5 en tawasuyu): churay direcciona sus blobs con blake3(bytes) pelado y takana iba a hacerlo
length-prefijado, bajo el mismo prefijo b3: ⇒ el mismo archivo con dos hex distintos, y un
CAS compartido que no falla ruidosamente sino que busca nombres distintos para los mismos bytes.
Ahora usa ArtifactHash::of_bytes — que ya existía en takana y ya era blake3 pelado; es la
convención de of_file y la del expected_hash de un .swm, así que of_inputs era además la
pieza fuera de sitio dentro del propio repo. Coste del cambio: una línea, porque ningún índice
publicado llevaba todavía el campo. Es el argumento de este ADR aplicado a sí mismo — decidir
antes de que haya usuarios. Hay guardián: digest_es_blake3_pelado_y_no_length_prefijado.
El campo es Option con skip_serializing_if, así que un índice firmado antes de este cambio
serializa idéntico y su firma sigue siendo válida (hay test). Un índice sin digests no bloquea,
pero dice cuántos paquetes no verificó: un índice viejo servido desde un espejo ajeno no es
seguro, y el silencio lo haría parecer verificado.
6. Cloudflare R2 como primer origen público — instancia, no arquitectura
El criterio no es el precio del almacenamiento (51 G no cuestan nada en ningún sitio): es el
egress, que es lo que quiebra a los proyectos que reparten binarios. R2 no cobra salida.
Órdenes de magnitud, a verificar el día de contratar — no son cotizaciones:
| origen | almacenamiento | egress |
| Storage Box BX11 (ya pagado) | €3,20/mes por 1 TiB | sin cargo, pero no es un CDN |
| Cloudflare R2 | ~US$0,015/GB-mes ⇒ <US$1/mes por 51 G | 0 |
| Backblaze B2 + Cloudflare | ~US$6/TB-mes | 0 vía Bandwidth Alliance |
| Hetzner Object Storage | ~€6/mes (1 TB + 1 TB) | incluido hasta el TB |
Lo que decide este ADR no es «usamos R2»: es que R2 sea una entrada más de una lista. Si mañana
cierra la cuenta, se añade otra base a la lista y no cambia una línea de código ni se re-hashea un
artefacto. La condición de no depender de un servidorcito no se cumple contratando un proveedor
grande — se cumple pudiendo perder cualquiera de ellos sin enterarse.
Regla que se deriva: nunca menos de dos orígenes en dos proveedores distintos. Hoy el repo, el
respaldo y el mirror de fuentes están los tres en Hetzner y en la misma cuenta — riesgo que el ADR
0013 ya dejó anotado sin resolver, y que R2 resuelve por ser de otra casa.
7. ⛔ Lo que NUNCA sale hacia el CDN
La clave raíz de firma, que es offline y es lo único no delegable de todo el lanzamiento
(SDD 19 §3.2, SDD 20). Un
CDN sirve bytes públicos; firmar es lo contrario de publicar. Si la clave estuviera en el sitio desde
donde se sirve, los N espejos dejarían de ser «orígenes sin confianza» y pasarían a ser N copias de
la autoridad.
Verificado de punta a punta, no por inspección
Dos servidores HTTP locales con el mismo index.json (mismo nombre de paquete, mismo file,
mismo digest) y .swm distintos: el bueno y una copia con un solo byte cambiado — el mismo
tamaño, el mismo nombre de fichero. Es exactamente lo que puede hacer un espejo que no controlamos.
| escenario | resultado | qué prueba |
| 1. sólo el origen bueno | pasa el digest, baja, carga el .swm, y muere después en el config_edit (el paquete sintético toca un /etc/network.conf que no existe) | la descarga y la verificación se cruzan de verdad |
| 2. sólo el origen manipulado | Error: digest de 'demo' no coincide … el origen entregó un .swm distinto del que se firmó, exit 1 | el byte cambiado se detecta |
| 3. origen muerto primero (:9999), bueno detrás | llega al mismo punto que (1), y reporta bajados 1 .swm de http://127.0.0.1:8801 | la lista hace failover ante ausencia |
| 4. origen manipulado primero, bueno detrás | aborta con el mismo error que (2), exit 1 | NO cae al siguiente ante contenido distinto — §4 |
(3) y (4) son el par que importa: sin el segundo, la lista sería un mecanismo para tapar espejos
mentirosos en vez de para sobrevivir a espejos caídos.
Además, el mensaje de (3) nombra el origen que sirvió de verdad, no la lista entera. Decir
«bajado de <lista>» cuando sólo uno respondió es una media verdad que esconde justo el dato con el
que se depura un espejo caído — el mismo error de diagnóstico que el ADR 0013 documenta en el
poblador de bundles.
Consecuencias
- Cero re-hasheos. La URL no está en
hash_inputs y digest no es un input de build.
- Cero roturas de firmas existentes. Verificado por test: sin
digest, PackageEntry serializa
idéntico a antes.
- Un espejo nuevo pasa a ser un
rsync plano sin índice, sin coordinación y sin confianza. La
barrera de entrada para que un tercero nos espeje es más baja que la de una distro clásica, y eso
es una consecuencia directa del direccionamiento por contenido, no un favor que pedimos.
- Aparece deuda de vigilancia (ver abajo): más orígenes = más sitios que pueden pudrirse en silencio.
Lo que este ADR NO decide
- El vigía de los espejos.
scripts/fuentes/fuentes-vigia.sh vigila upstream, no nuestros
orígenes. Con una lista de N, hace falta comprobar que cada uno tiene lo que dice tener — si no,
volvemos a la métrica que envejece hacia el optimismo. Es el trabajo pendiente más claro que deja
este documento.
- Qué origen es la verdad al publicar (cómo se propaga un release de uno a los demás, y qué pasa
si la propagación se corta a media tanda: un espejo con índice nuevo y
.swm viejo).
- Las imágenes de instalación: son ficheros grandes de un solo tiro y el reparto natural es
torrent con webseed, que no comparte mecánica con esto.
- La rotación de claves de firma y el procedimiento ante filtración — sigue siendo de
SDD 19 §3.2, y sin él la firma sigue siendo teatro por muchos
orígenes que haya.
- La concentración en
github.com (64% del corpus como fuente). Este ADR no la toca; sigue
pendiente desde el 0013.