Files
takana/recipes/steam-runtime-sniper.toml
T
SergioandClaude Opus 5 fdce080d96 qorpa D8: sniper sellado al store, con la marca que evita que la cifra mienta
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
2026-09-04 00:09:20 +00:00

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 = []