tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

dunst-headless: `--via-atuq` — la cadena entera, empezando en una página web

El modo por defecto prueba la cadena del SISTEMA (`notify-send` → bus → dunst).
Éste prueba la que motivó todo el hilo del §6.10: una página llama a
`new Notification(...)`, `libxul` hace `dlopen("libnotify.so.4")` —que no es
NEEDED de ningún ELF, o sea invisible para cualquier auditor—, eso habla D-Bus,
el bus ACTIVA dunst y dunst dibuja. Cinco piezas, y la única forma de saber que
están las cinco es verlo.

El veredicto acá no puede ser el diff de píxeles solo: el navegador ocupa la
pantalla y repinta por su cuenta. Lo decisivo es el `onshow` del objeto
Notification, que es el motor diciendo que el sistema ACEPTÓ la notificación; el
diff queda como corroboración y la captura «antes» se toma con atuq ya pintado.

    --via-atuq                     MOSTRADA #33 · 151.174 píxeles
    --via-atuq --negative-control  «ERROR al mostrar» · 0 píxeles

El control negativo también se lee distinto según el modo, y no por comodidad:
sin el .service, lo que tiene que faltar en el modo navegador es el onshow.

Captura en docs/evidencia/atuq-notificacion-web-sway-2026-09-07.png: dos globos
de dunst sobre la ventana de atuq, en sway headless, sin que nadie haya lanzado
el daemon a mano.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs

ec45c3fa975a8e164dc84f28508752d499658528 · ruwaq Sergio · (1 killa ñawpaq) · tayta ca7a410b5 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

2 qillqakuna · +98 −5

diff --git a/docs/evidencia/atuq-notificacion-web-sway-2026-09-07.png b/docs/evidencia/atuq-notificacion-web-sway-2026-09-07.png
new file mode 100644
index 00000000..047d5ef1
Binary files /dev/null and b/docs/evidencia/atuq-notificacion-web-sway-2026-09-07.png differ
diff --git a/scripts/wlr/dunst-headless.sh b/scripts/wlr/dunst-headless.sh
index 250c4fae..47c16555 100755
--- a/scripts/wlr/dunst-headless.sh
+++ b/scripts/wlr/dunst-headless.sh
@@ -3,6 +3,7 @@
 #
 #   scripts/wlr/dunst-headless.sh                     # sale 0 si la notificación se dibuja
 #   scripts/wlr/dunst-headless.sh --negative-control  # borra el .service y exige que NO se vea
+#   scripts/wlr/dunst-headless.sh --via-atuq          # el emisor es una PÁGINA WEB en el navegador
 #
 # ── QUÉ PRUEBA, QUE NO ES LO MISMO QUE «ESTÁ INSTALADO» ───────────────────────────────────────
 # Tener `libnotify` en la imagen es media función: el que la completa es un daemon escuchando
@@ -18,6 +19,17 @@
 # a otro lado, o dunst no supiera hablar wayland, este guion no vería nada. Es el mismo camino que
 # recorre una página web pidiendo notificar en `atuq`.
 #
+# ── `--via-atuq`: LA MISMA CADENA, PERO EMPEZANDO EN UNA PÁGINA WEB ───────────────────────────
+# El modo por defecto prueba la cadena del SISTEMA. Éste prueba la que le importa al usuario y la
+# que motivó todo el hilo del SDD 26 §6.10: una página llama a `new Notification(...)`, `libxul`
+# hace `dlopen("libnotify.so.4")` —que no es NEEDED de ningún ELF, o sea invisible para todo
+# auditor—, eso habla D-Bus, el bus activa dunst y dunst dibuja. Cinco piezas, y la única forma de
+# saber que están las cinco es verlo.
+# El veredicto acá NO puede ser sólo el diff de píxeles —el navegador ocupa la pantalla y cambia
+# solo—, así que la captura «antes» se toma con atuq YA ABIERTO y la página espera 25 s antes de
+# notificar. Y se exige además que el `onshow` del objeto Notification haya disparado, que es el
+# motor diciendo que el sistema aceptó la notificación.
+#
 # ── POR QUÉ notify-send Y NO dunstify ─────────────────────────────────────────────────────────
 # Porque `dunstify` no se shipea: mezcla la GLib ESTÁTICA del corpus con la COMPARTIDA que arrastra
 # `libnotify.so.4` y se cae con SIGSEGV hasta en `--help` (dos copias de GObject en un proceso — el
@@ -35,7 +47,14 @@ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
 RFS="${RFS:-/mnt/cosecha/escritorios/sway-rootfs}"
 ALPINE="$ROOT/.dev-fs/alpine"
 CONTROL=0
-[ "${1:-}" = "--negative-control" ] && CONTROL=1
+ATUQ=0
+for a in "$@"; do
+  case "$a" in
+    --negative-control) CONTROL=1 ;;
+    --via-atuq)         ATUQ=1 ;;
+    *) echo "✗ opción desconocida: $a" >&2; exit 1 ;;
+  esac
+done
 
 [ -d "$RFS" ] || { echo "✗ no está el rootfs en $RFS — corré antes:" >&2
                    echo "  scripts/hydrate-profile.py escritorio-sway --into $RFS" >&2; exit 1; }
@@ -62,13 +81,26 @@ mkdir -p "$XDG_RUNTIME_DIR" /root; chmod 700 "$XDG_RUNTIME_DIR"
   # sesión de verdad. Al revés, dunst arrancaría sin display y saldría con status 1.
   eval "$(/usr/bin/dbus-daemon --session --fork --print-address=1 | { read a; echo "DBUS_SESSION_BUS_ADDRESS=$a"; })"
   export DBUS_SESSION_BUS_ADDRESS
-  /usr/bin/grim /salida/antes.png
+  [ "${ATUQ:-0}" = "1" ] || /usr/bin/grim /salida/antes.png
   echo "@@ dueño ANTES: $(timeout 10 /usr/bin/dbus-send --session --dest=org.freedesktop.DBus \
       --print-reply=literal /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner \
       string:org.freedesktop.Notifications 2>&1 | tr -d ' \n')"
-  timeout 25 /usr/bin/notify-send -u critical "hammer" "dunst lo activó el bus" 2>&1
-  echo "@@ notify-send rc=$?"
-  sleep 5
+  if [ "${ATUQ:-0}" = "1" ]; then
+    export MOZ_ENABLE_WAYLAND=1 GDK_BACKEND=wayland LIBGL_DRIVERS_PATH=/usr/lib/dri
+    # Los tres sandboxes de Gecko se apagan porque esto YA corre dentro de una jaula sin userns
+    # anidado: con ellos puestos los procesos hijos mueren en bucle. Es entorno de prueba, no una
+    # preferencia del artefacto.
+    export MOZ_DISABLE_CONTENT_SANDBOX=1 MOZ_DISABLE_RDD_SANDBOX=1 MOZ_DISABLE_UTILITY_SANDBOX=1
+    /usr/bin/atuq --no-remote --profile /salida/perfil file:///salida/notificar.html \
+        > /salida/atuq.log 2>&1 &
+    sleep 22                       # que el navegador ya esté pintado ANTES de la captura «antes»
+    /usr/bin/grim /salida/antes.png
+    sleep 12                       # la página notifica a los 25 s y repite cada 5
+  else
+    timeout 25 /usr/bin/notify-send -u critical "hammer" "dunst lo activó el bus" 2>&1
+    echo "@@ notify-send rc=$?"
+    sleep 5
+  fi
   echo "@@ servidor: $(timeout 10 /usr/bin/dbus-send --session --dest=org.freedesktop.Notifications \
       --print-reply=literal /org/freedesktop/Notifications \
       org.freedesktop.Notifications.GetServerInformation 2>&1 | tr -s ' ')"
@@ -79,11 +111,50 @@ mkdir -p "$XDG_RUNTIME_DIR" /root; chmod 700 "$XDG_RUNTIME_DIR"
 exec /usr/bin/sway -d 2>&1
 ADENTRO
 
+if [ "$ATUQ" = "1" ]; then
+  mkdir -p "$OUT/perfil"
+  cat > "$OUT/perfil/user.js" <<'JS'
+// `dump()` es el canal por el que la página le habla al log del proceso: sin red y sin servidor.
+user_pref("browser.dom.window.dump.enabled", true);
+user_pref("devtools.console.stdout.content", true);
+// 1 = permitir sin preguntar. Sin esto la prueba mediría el POPUP DE PERMISOS del navegador, que
+// se dibuja igual y no prueba nada del sistema de notificaciones.
+user_pref("permissions.default.desktop-notification", 1);
+user_pref("browser.shell.checkDefaultBrowser", false);
+JS
+  cat > "$OUT/notificar.html" <<'HTML'
+<!doctype html><meta charset="utf-8"><title>prueba de notificación</title>
+<body style="background:#102a43;color:#eee;font:20px monospace;padding:30px">
+<h1 id="e">pidiendo permiso…</h1>
+<script>
+const e = document.getElementById('e');
+function log(s){ e.textContent = s; if (window.dump) dump("NOTIF-TEST " + s + "\n"); }
+log('permiso inicial: ' + Notification.permission);
+Notification.requestPermission().then(p => {
+  log('permiso: ' + p);
+  if (p !== 'granted') { log('DENEGADO'); return; }
+  // La primera a los 25 s: la captura «antes» se toma a los 22, con el navegador ya pintado.
+  // Y se repite cada 5 porque la urgencia normal de dunst caduca a los 10 s — lo que se fotografía
+  // es que el camino funcione, no atinarle al instante.
+  let i = 0;
+  const tic = () => {
+    const n = new Notification('atuq', {body: 'una página web notificó (' + (++i) + ')'});
+    n.onshow  = () => log('MOSTRADA #' + i);
+    n.onerror = () => log('ERROR al mostrar');
+  };
+  setTimeout(tic, 25000);
+  setInterval(tic, 5000);
+});
+</script>
+HTML
+fi
+
 # `--unshare-pid` para que un `ps` de adentro no vea la máquina entera, y el lab de alpine DEBAJO
 # porque el corpus no produce el intérprete musl (`ld-musl-x86_64.so.1`): sin él ni sway arranca.
 timeout 180 bwrap --overlay-src "$ALPINE" --overlay-src "$RFS" --tmp-overlay / \
   --bind "$OUT" /salida --dev /dev --proc /proc --tmpfs /run --tmpfs /tmp --unshare-pid \
   --uid 0 --gid 0 --setenv PATH /usr/bin:/bin:/usr/sbin:/sbin --setenv CONTROL "$CONTROL" \
+  --setenv ATUQ "$ATUQ" \
   /bin/sh /salida/dentro.sh > "$OUT/log.txt" 2>&1 || true
 
 grep -a '@@' "$OUT/log.txt" || true
@@ -107,12 +178,34 @@ PY
 echo "  píxeles cambiados en el cuadrante de dunst: $CAMBIADOS"
 
 if [ "$CONTROL" = "1" ]; then
+  # El control se lee distinto según el modo, y no por comodidad: en `--via-atuq` el navegador ocupa
+  # la pantalla y repinta solo —el diff de píxeles cambia miles aunque no haya UNA notificación—, así
+  # que ahí lo que tiene que faltar es el `onshow` del motor. En el modo normal la pantalla está
+  # quieta y el diff sí es el veredicto.
+  if [ "$ATUQ" = "1" ]; then
+    if grep -aq 'NOTIF-TEST MOSTRADA' "$OUT/atuq.log" 2>/dev/null; then
+      echo "✗ control negativo: el navegador dijo MOSTRADA sin el .service — este guion no prueba lo que dice" >&2
+      exit 1
+    fi
+    echo "✓ control negativo: sin el .service el navegador nunca llega a MOSTRADA"
+    grep -a 'NOTIF-TEST' "$OUT/atuq.log" 2>/dev/null | tail -2 || true
+    exit 0
+  fi
   if [ "$CAMBIADOS" -lt 500 ]; then
     echo "✓ control negativo: sin el .service no se dibuja nada (el guardián SÍ lo vería)"; exit 0
   fi
   echo "✗ control negativo: se dibujó una notificación SIN el .service — este guion no prueba lo que dice" >&2
   exit 1
 fi
+if [ "$ATUQ" = "1" ]; then
+  # El motor diciendo que el sistema ACEPTÓ la notificación. Es la mitad que el diff de píxeles no
+  # puede distinguir de «el navegador repintó algo».
+  grep -aq 'NOTIF-TEST MOSTRADA' "$OUT/atuq.log" || {
+    echo "✗ el navegador nunca llegó a MOSTRADA — ¿libxul no encontró libnotify.so.4?" >&2
+    grep -a 'NOTIF-TEST' "$OUT/atuq.log" | tail -3 >&2 || true
+    cp "$OUT/atuq.log" work/ 2>/dev/null || true; exit 1; }
+  echo "  el motor: $(grep -a 'NOTIF-TEST MOSTRADA' "$OUT/atuq.log" | tail -1)"
+fi
 [ "$CAMBIADOS" -ge 500 ] || {
   echo "✗ la notificación no se dibujó ($CAMBIADOS píxeles) — capturas en $OUT" >&2
   cp "$OUT"/*.png "$OUT/log.txt" work/ 2>/dev/null || true; exit 1; }