qorpa export: shims generados, y la clase ajeno para que nadie los cuente mal

Paso 5 del ADR 0015, sus dos mitades.

SHIMS. `hammer qorpa export <id>` genera lanzadores finos y `.desktop` en el
espacio del host, desde lo DECLARADO en `[export]` — nunca todo: exportar todo
haría que el `ls` de la imagen compita con el nuestro, que es la falla de
Bedrock (arbitra en tiempo de exec, por heurística). Gana en tres cosas contra
un FUSE: cero costo en runtime, `cat` al shim y ves qué hace, y se revoca
borrándolos.

Se GENERAN, no se copian. El `.desktop` se arma con lista BLANCA de claves, así
que `Exec`, `TryExec`, `Path` y `DBusActivatable` quedan fuera por definición y
no por enumeración — una lista negra dejaría entrar la próxima clave ejecutable
que invente el estándar. El Exec original se cita en un comentario del fichero
generado, para que se vea qué decía y qué no se copió. El icono se busca en la
vista merged (upper primero, imagen después: si no, se perdería lo que instaló
el gestor de paquetes) y se copia al host, porque un icono que el host no
resuelve se ve como un cuadrito gris. Y `exported.json` registra cada fichero
escrito, para que `--remove` borre EXACTAMENTE eso y no por patrón sobre el
~/.local/bin de alguien.

Probado de punta a punta con un .desktop ajeno real de la imagen de Arch: el
shim corre `pacman -Q` del huésped desde el host, el X-KDE-Wayland-Interfaces
quedó fuera, el icono viajó, y --remove dejó 0 ficheros con la instancia intacta.

CLASE `ajeno`. build-state.py inyecta los nodos declarados en el nuevo
docs/state/qorpa-ajenos.toml ANTES que los `wanted`, y ese orden es la mitad del
punto: un nodo que provee una imagen ajena no es una receta por escribir. Con
eso el `xwayland` de escritorio-kde deja de ser deuda y pasa a contarse aparte:

  escritorio-kde       187/188 listo   falta   1  (raíces 14, + 1 ajenas)

Dos decisiones que sostienen esa cifra: los ajenos se RESTAN del denominador (si
entraran, el número que se lee como "cuánto construimos" crecería solo cada vez
que alguien enjaula una app), y la declaración vive en el REPO y no se lee de
/var/lib/hammer — build-state.json se commitea y lo regenera el cron en dos
máquinas; si la clase saliera de las instancias instaladas, cada una diría algo
distinto y se pisarían en cada cosecha. Es el error que ya se cometió con
sealed_remoto. Qué provee una imagen ajena es diseño; qué tenés instalado, no.

Un ajeno tampoco se hashea, y no por comodidad: no tiene procedencia de fuente,
así que un hash afirmaría que lo reproducimos.

2 tests nuevos (que del .desktop ajeno no sobreviva nada ejecutable; que el shim
no se rompa con rutas raras). 47/47.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
This commit is contained in:
Sergio
2026-09-03 06:15:19 +00:00
co-authored by Claude Opus 5
parent 3448ca4678
commit f64859bade
8 changed files with 495 additions and 65 deletions
+18 -2
View File
@@ -200,9 +200,20 @@ la hidratación del escritorio ya va por lista explícita (`TARGETS="…"`), nun
`ajeno`**, y el reporte pasa a decir:
```
escritorio-kde 171 nativas + 6 ajenas (ajenas: sin procedencia de fuente, sin repro)
escritorio-kde 187/188 listo falta 1 (raíces 14, + 1 ajenas)
```
Así sale hoy, de verdad: el `xwayland` que era `wanted` (deuda: receta por escribir) pasó a `ajeno`
y **la deuda se disolvió sin escribir la receta**. Dos decisiones que sostienen la cifra:
- **Los ajenos se restan del denominador.** Si entraran en la fracción, el número que todo el mundo
lee como «cuánto construimos» crecería solo cada vez que alguien enjaula una app ajena.
- **La declaración vive en `docs/state/qorpa-ajenos.toml`, en el repo — no se lee de
`/var/lib/hammer`.** `build-state.json` se commitea y lo regenera el cron en dos máquinas: si la
clase saliera de las instancias instaladas, cada una diría algo distinto y se pisarían en cada
cosecha. Es el mismo error que ya se cometió con `sealed_remoto`. **Qué provee una imagen ajena es
diseño, compartido; qué instancias tiene tu disco, no.**
Bedrock no puede decirte qué tenés. Nosotros sí, y esto cierra el riesgo real de todo el ADR: que en
seis meses alguien cuente esas apps como parte del corpus. **En otra clase no se pueden contar mal.**
@@ -421,7 +432,12 @@ Se escriben acá para que no se descubran en producción.
eslabón dentro de bwrap (cruza un binario **estático**, no una librería ⇒ D2 sigue en pie), y
aporta lo que bwrap no da: **seccomp** —hoy una instancia podría `io_uring`, `bpf`, `ptrace`,
`userfaultfd`, `keyctl`, `perf_event_open`—, `no_new_privs`, y el canal de evidencia. Ver D9.
5. Shims + `.desktop` generados (`export`), y clase `ajeno` en `build-state.py`.
5.**HECHO 2026-09-03.** Shims + `.desktop` generados (`export`), y clase `ajeno` en
`build-state.py`. El `.desktop` se genera con lista BLANCA de claves —`Exec`, `TryExec`, `Path`,
`DBusActivatable` quedan fuera **por definición**, no por enumeración— y el icono se copia al
host, porque un icono que el host no resuelve se ve como un cuadrito gris. Cada fichero escrito
queda en un registro (`exported.json`) para que `--remove` borre **exactamente** lo generado y no
por patrón sobre el `~/.local/bin` de alguien.
6. **Steam de punta a punta**, con verificación explícita del bwrap anidado.
7. Poda (`hammer qorpa prune`) y renglón en SDD 20 sobre licencias.
8. Proxy filtrante de Wayland — ticket propio, el más valioso de la lista.