D8 decía que sniper «entra al store por `file_drop`». Dos correcciones, y la primera es de vocabulario: **`file_drop` en hammer es otra cosa** — una operación de `hammer apply` que coloca un fichero en el sistema instalado verificando su hash. No tenía nada que ver con sellar. Lo que sella es lo de siempre, una receta. Queda escrito en el ADR: un término inventado que suena a mecanismo existente manda a buscar el código donde no está. `recipes/steam-runtime-sniper.toml` sella el árbol del runtime (11196 ficheros) pineado por el sha256 que ya estaba verificado. Entra donde Arch y Ubuntu no pueden por una propiedad, no por simpatía: **no muta** —nadie le instala nada adentro— así que el mismo tarball da siempre el mismo árbol y sellarlo es una afirmación verdadera. **La marca: `foreign = true`.** No cambia el build en un byte y **no entra en `hash_inputs`** (describe procedencia, no identidad — hay test). Lo que cambia es contable: `build-state.py` la clasifica `ajeno`, la resta del denominador de las imágenes y la deja fuera del recuento de recetas. Sin eso, sellar un prebuilt habría subido la cifra que todo el mundo lee como «cuánto construimos» — el riesgo que el ADR escribió antes de que existiera la primera instancia. Verificado: sigue diciendo 821 recetas, y aparte `de las ajenas, 1 selladas al store (prebuilt pineado, sin procedencia de fuente)`. Y la diferencia con el otro ajeno: `xwayland` no se hashea (no hay receta, y un hash afirmaría que lo reproducimos); el sellado **sí conserva su hash**, porque está en el store y que un artefacto exista mientras el grafo lo niega sería otra forma de mentir. Comparten el estado, que es lo que protege la cifra. **`hammer qorpa import --from-store <hash>`** lo consume, y ahí está el detalle que hace que valga: la imagen se registra bajo el **sha256 del archivo de upstream**, no bajo el ArtifactHash. Al revés, la imagen del store y la traída con `pull` serían dos imágenes distintas con los mismos bytes y las instancias de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar. El árbol se **enlaza**: una imagen nunca se escribe (lo que escribe la instancia va a su `upper`), así que compartir inodos con un artefacto sellado y de sólo lectura es correcto por construcción y la imagen cuesta ~0 bytes. La contracara conocida de `.dmerge`: mientras el artefacto siga en el store, borrar la imagen no libera disco; `--copy` lo evita. Licencia `LicenseRef-qorpa-ajena-no-enumerable` a propósito: adentro hay cientos de paquetes Debian y no podemos enumerarlos; vacío se leería como «todavía no la poblamos». SDD 20 lo recoge y afila la distinción: replicarla a nuestras máquinas es lo que ya hace ADR 0013 con las fuentes; publicarla a terceros sigue pidiendo licencia y marca. 29 tests verdes. El sellado en sí corre aparte, esperando el lock de la granja. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q
91 lines
5.8 KiB
TOML
91 lines
5.8 KiB
TOML
# steam-runtime-sniper — el runtime en el que Valve corre los juegos, SELLADO al store.
|
|
# ADR 0015 (qorpa) D8: es la única imagen ajena de la terna que puede entrar acá, y esta receta
|
|
# es lo que hace cierto ese renglón.
|
|
#
|
|
# ══ ESTO NO ES UNA RECETA NORMAL, Y LA DIFERENCIA IMPORTA ══════════════════════════════════════
|
|
# No construye nada: **sella bytes ajenos ya compilados**. Por eso lleva `foreign = true`, que no
|
|
# cambia el build en nada y sí cambia cómo la cuenta el grafo — pasa a clase `ajeno`, se resta del
|
|
# denominador de las imágenes y NO engorda «cuántas recetas construimos». Sin esa marca, sellar un
|
|
# prebuilt inflaría la cifra que todo el mundo lee como corpus, que es exactamente el riesgo que el
|
|
# ADR 0015 se puso por escrito para no cometer.
|
|
#
|
|
# ══ POR QUÉ ESTA SÍ Y LAS OTRAS DOS NO ═════════════════════════════════════════════════════════
|
|
# La invariante D1 dice que las imágenes ajenas NO entran al store, y el motivo es que un rootfs
|
|
# mutable —Arch, Ubuntu, al que le instalás cosas— no reproduce: meterlo haría que el store mienta.
|
|
# Sniper es distinto por una propiedad, no por simpatía: **no muta**. Nadie le instala nada adentro;
|
|
# pressure-vessel lo monta de sólo lectura. Así que el mismo tarball pineado produce siempre el
|
|
# mismo árbol, y sellarlo es una afirmación verdadera.
|
|
#
|
|
# ══ QUÉ SE GANA SELLÁNDOLO ═════════════════════════════════════════════════════════════════════
|
|
# Cadena de custodia NUESTRA: el artefacto se replica con `hammer mirror push`, su digest puede ir
|
|
# al índice firmado (ADR 0014) y una instancia se arma sin depender de que la CDN de Valve siga
|
|
# viva. Se consume con:
|
|
#
|
|
# hammer qorpa import --from-store <hash> # ⇒ images/<sha256 de Valve>/{image.toml,rootfs}
|
|
#
|
|
# La identidad de la imagen sigue siendo **el sha256 del ARCHIVO de Valve**, no el ArtifactHash: es
|
|
# lo que hace que dos máquinas coincidan aunque una lo traiga del store y la otra de upstream. Por
|
|
# eso el `image.toml` viaja DENTRO del artefacto, escrito acá y no adivinado por el importador.
|
|
#
|
|
# ══ POR QUÉ EL ÁRBOL VA A /usr/share/qorpa Y NO A LA RAÍZ ══════════════════════════════════════
|
|
# Un rootfs glibc entero volcado en `/` pisaría el sistema; y aunque la tentación sea dejarlo en la
|
|
# raíz del artefacto para que `hydrate` lo escupa con la forma exacta de una imagen qorpa, un
|
|
# artefacto tiene que ser inofensivo si alguien lo compone en un rootfs por error. Namespaceado no
|
|
# colisiona con nada. **Esta receta NO va en ningún perfil de `targets.toml`**: no es parte de
|
|
# ninguna imagen nuestra, es carga para qorpa.
|
|
#
|
|
# ══ LICENCIA ══════════════════════════════════════════════════════════════════════════════════
|
|
# `LicenseRef-…-no-enumerable` a propósito: adentro hay cientos de paquetes Debian con sus términos
|
|
# y no podemos enumerarlos. Poner "MIT" o dejarlo vacío sería peor — vacío se lee como «todavía no
|
|
# la poblamos». SDD 20 ya dice que las ajenas no entran al reporte de licencias; esto lo hace
|
|
# visible desde la receta.
|
|
name = "steam-runtime-sniper"
|
|
version = "3.0.20260805.254768"
|
|
license = "LicenseRef-qorpa-ajena-no-enumerable"
|
|
foreign = true
|
|
|
|
[source]
|
|
# El snapshot NO es «el último»: es el que `latest-container-runtime-depot.txt` dice que despliega
|
|
# el cliente de Steam. Pinear otro sería pinear algo que nadie corre. Digest verificado contra el
|
|
# SHA256SUMS del propio snapshot el 2026-09-03 (ver docs/state/qorpa-imagenes.toml).
|
|
tarball = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/com.valvesoftware.SteamRuntime.Platform-amd64%2Ci386-sniper-runtime.tar.gz"
|
|
sha256 = "1a293c312215c59e4e5348ce210594cd7967d1a1fcaec6248a024e0878776f2b"
|
|
# CERO, no el 1 de siempre: el archivo trae DOS entradas arriba —`files/` (el rootfs) y `metadata`
|
|
# (la ficha de build de Valve)—. Con strip 1 el rootfs se fundiría con su hermano y el árbol
|
|
# quedaría mal anclado, que es el fallo que no rompe al traerlo sino después, cuando la instancia
|
|
# no encuentra su loader.
|
|
strip_components = 0
|
|
|
|
[build]
|
|
compiler = "zig-cc"
|
|
target = "x86_64-linux-musl"
|
|
link = "dynamic"
|
|
|
|
[build.phases]
|
|
configure = "true"
|
|
compile = "true"
|
|
# `cp -a` y no `cp -r`: hay que preservar symlinks, modos y tiempos o el rootfs deja de ser un
|
|
# rootfs. `metadata` se deja fuera igual que hace `hammer qorpa pull`, para que el árbol sellado y
|
|
# el traído de upstream sean el MISMO árbol y no dos primos.
|
|
install = '''
|
|
set -e
|
|
D=/out/usr/share/qorpa/images/steam-runtime-sniper
|
|
mkdir -p "$D"
|
|
cp -a files "$D/rootfs"
|
|
test -x "$D/rootfs/usr/bin/env" || { echo "el árbol no parece un rootfs" >&2; exit 1; }
|
|
cat > "$D/image.toml" <<'EOF'
|
|
# Lo sella `recipes/steam-runtime-sniper.toml` (ADR 0015 D8) y lo consume `hammer qorpa import`.
|
|
# La identidad de la imagen es el sha256 del ARCHIVO de Valve, no el ArtifactHash de este
|
|
# artefacto: es lo que hace que coincida con la que otra máquina traiga de upstream.
|
|
sha256 = "1a293c312215c59e4e5348ce210594cd7967d1a1fcaec6248a024e0878776f2b"
|
|
url = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/com.valvesoftware.SteamRuntime.Platform-amd64%2Ci386-sniper-runtime.tar.gz"
|
|
label = "sniper-platform-3.0.20260805.254768"
|
|
archive_bytes = 302423522
|
|
rootfs_subdir = "files"
|
|
dropped_siblings = ["metadata"]
|
|
EOF
|
|
'''
|
|
|
|
[deps]
|
|
build = []
|