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…

atuq: la página de inicio y la pestaña nueva, verificadas contra el propio motor

La v0.3 las puso por extensión de sistema y quedó como afirmación. Que el XPI
esté en el artefacto no dice nada: el override lo puede rechazar el gestor de
extensiones, lo puede pisar una política, o el navegador puede arrancar con el
chrome viejo cacheado. Ninguna de las tres falla ruidosamente — se ve una pestaña
nueva perfectamente normal, que es de otro.

Se mide preguntándole AL MOTOR, no mirando el disco: una sonda con permiso
`browserSettings` lee `homepageOverride` y `newTabPageOverride`.

    positivo  moz-extension://…/inicio.html  (las dos)
    control   about:home · about:newtab

El control negativo borra el XPI de `inicio` dentro del overlay temporal —el
rootfs real no se toca— y exige el resultado contrario.

Y una escotilla `ATUQ_DIR` en las dos sondas nuevas, con su aviso a gritos: el
corpus es compartido y hoy mismo el perfil PGO v2 de otro frente re-hasheó
`firefox` y con él `atuq`, así que el artefacto VIGENTE no existe en ningún store
hasta que alguien pague un build de horas. Sin escotilla no se puede correr una
sola prueba de atuq en esa ventana; con ella se corre contra un artefacto viejo A
SABIENDAS, y por eso el resultado no se puede citar como «atuq de hoy pasa».

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

639948f2cd228af012017c46a57fa7b37a753f56 · ruwaq Sergio · (1 killa ñawpaq) · tayta 94d7e8626 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 5

scripts/test-atuq-inicio.py

scripts/test-atuq-nativo.py

2 qillqakuna · +196 −0

diff --git a/scripts/test-atuq-inicio.py b/scripts/test-atuq-inicio.py
new file mode 100644
index 00000000..d16662ca
--- /dev/null
+++ b/scripts/test-atuq-inicio.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+"""¿La página de inicio y la pestaña nueva de `atuq` son las NUESTRAS? (SDD 26 §4.e)
+
+    python3 scripts/test-atuq-inicio.py                     # sale 0 si las dos son de atuq
+    python3 scripts/test-atuq-inicio.py --negative-control  # sin el XPI: exige que sean las de Firefox
+
+La v0.3 puso la página de inicio y la pestaña nueva **por extensión de sistema**, con
+`chrome_url_overrides.newtab` y `chrome_settings_overrides.homepage`. Que el XPI esté en el
+artefacto no dice nada de eso: un override lo puede rechazar el gestor de extensiones, lo puede
+pisar una política, y —el modo de fallo propio de este proyecto— el navegador puede arrancar con el
+chrome VIEJO cacheado y enseñar la página de Firefox como si nada. Ninguna de las tres cosas
+falla ruidosamente: se ve una pestaña nueva perfectamente normal, que es de otro.
+
+── CÓMO SE MIDE, SIN ABRIR PESTAÑAS NI MIRAR PÍXELES ─────────────────────────────────────────
+Una extensión sonda con permiso `browserSettings` le pregunta al propio navegador cuál es su
+`homepageOverride` y su `newTabPageOverride`. Es la respuesta del motor, no una inferencia: si el
+override de `inicio` agarró, las dos son `moz-extension://…/inicio.html`; si no, son las de fábrica
+(`about:home` / `about:newtab`). Mirar el XPI en el disco, o el `manifest.json` dentro del XPI,
+mediría lo declarado — que es justo lo que ya sabíamos.
+
+El control negativo BORRA el XPI de `inicio` dentro del overlay temporal (el rootfs real no se toca)
+y exige el resultado contrario. Sin él, una sonda rota que siempre dijera «es la nuestra» se vería
+idéntica a una que funciona.
+
+REQUISITOS: el rootfs de `scripts/atuq-nested.sh` (que lo hidrata) y `atuq` sellado.
+"""
+import json
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import zipfile
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+RFS = os.environ.get("RFS", "/mnt/cosecha/escritorios/atuq-rootfs")
+ALPINE = os.path.join(ROOT, ".dev-fs/alpine")
+HAMMER = os.path.join(ROOT, "target/release/hammer")
+STORE = os.environ.get("HAMMER_STORE", os.path.join(ROOT, "store"))
+
+EXT_ID = "sonda-inicio@atuq.tawasuyu"
+XPI_INICIO = "/usr/lib/atuq/distribution/extensions/inicio@atuq.tawasuyu.xpi"
+
+MANIFIESTO = {
+    "manifest_version": 2,
+    "name": "sonda-inicio",
+    "version": "1.0",
+    "browser_specific_settings": {"gecko": {"id": EXT_ID}},
+    "permissions": ["browserSettings"],
+    "background": {"scripts": ["fondo.js"]},
+}
+
+FONDO = """
+function di(s) { try { dump("INICIO-TEST " + s + "\\n"); } catch (e) {} }
+async function mirar() {
+  try {
+    const h = await browser.browserSettings.homepageOverride.get({});
+    const n = await browser.browserSettings.newTabPageOverride.get({});
+    di("HOMEPAGE " + h.value);
+    di("NEWTAB " + n.value);
+  } catch (e) {
+    di("EXCEPCIÓN " + e.message);
+  }
+}
+mirar();
+"""
+
+
+def fatal(msg):
+    print(f"✗ {msg}", file=sys.stderr)
+    raise SystemExit(1)
+
+
+def artefacto(receta, nombre):
+    """El artefacto VIGENTE, por `hammer hash` y nunca por glob."""
+    # ⚠ ESCOTILLA `ATUQ_DIR`, y con su aviso puesto. El corpus es compartido: otro frente puede
+    # mover una receta de arriba —el 2026-09-07 el perfil PGO v2 re-hasheó `firefox` y con él
+    # `atuq`— y entonces el artefacto VIGENTE no existe en ningún store hasta que alguien pague un
+    # build de horas. Sin escotilla, ninguna prueba de atuq se puede correr en esa ventana. Con
+    # escotilla, se puede correr contra un artefacto viejo A SABIENDAS: por eso grita, y por eso el
+    # resultado no se puede citar como «atuq de hoy pasa».
+    forzado = os.environ.get("ATUQ_DIR")
+    if forzado and nombre == "atuq":
+        if not os.path.isdir(forzado):
+            fatal(f"ATUQ_DIR={forzado} no existe")
+        print(f"⚠ ATUQ_DIR: se prueba {os.path.basename(forzado)}, que NO es necesariamente el vigente")
+        return forzado
+
+    r = subprocess.run([HAMMER, "--store", STORE, "hash", f"recipes/{receta}.toml"],
+                       cwd=ROOT, capture_output=True, text=True)
+    if r.returncode != 0:
+        fatal(f"no pude hashear {receta}: {r.stderr.strip()}")
+    h = r.stdout.strip().splitlines()[-1].removeprefix("b3:")
+    d = os.path.join(STORE, f"{h}-{nombre}")
+    if not os.path.isdir(d):
+        fatal(f"{nombre} b3:{h[:8]} no está sellado — construilo antes")
+    return d
+
+
+def main():
+    negativo = "--negative-control" in sys.argv[1:]
+    if negativo:
+        print("== MODO CONTROL NEGATIVO: se BORRA el XPI de inicio y se exige la página de Firefox")
+    if not os.path.isdir(RFS):
+        fatal(f"no está el rootfs en {RFS} — corré antes `scripts/atuq-nested.sh`, que lo hidrata")
+    atuq = artefacto("atuq", "atuq")
+    print(f"artefacto: {os.path.basename(atuq)}")
+
+    tmp = tempfile.mkdtemp(prefix="atuq-inicio-")
+    salida = os.path.join(tmp, "salida")
+    extdir = os.path.join(tmp, "capa", "usr", "lib", "atuq", "distribution", "extensions")
+    os.makedirs(os.path.join(salida, "perfil"))
+    os.makedirs(extdir)
+    try:
+        with zipfile.ZipFile(os.path.join(extdir, f"{EXT_ID}.xpi"), "w") as z:
+            z.writestr("manifest.json", json.dumps(MANIFIESTO, indent=1))
+            z.writestr("fondo.js", FONDO)
+
+        with open(os.path.join(salida, "perfil", "user.js"), "w") as fh:
+            fh.write('user_pref("browser.dom.window.dump.enabled", true);\n')
+            fh.write('user_pref("browser.shell.checkDefaultBrowser", false);\n')
+
+        borrar = f"rm -f {XPI_INICIO}" if negativo else "true"
+        guion = f"""
+        mkdir -p /root
+        [ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id
+        {borrar}
+        timeout 50 /usr/bin/atuq --profile /salida/perfil --no-remote --headless about:blank \
+            > /salida/consola.log 2>&1
+        exit 0
+        """
+        subprocess.run([
+            "bwrap",
+            "--overlay-src", ALPINE, "--overlay-src", RFS, "--overlay-src", atuq,
+            "--overlay-src", os.path.join(tmp, "capa"), "--tmp-overlay", "/",
+            "--dev-bind", "/dev", "/dev", "--proc", "/proc", "--bind", "/sys", "/sys",
+            "--tmpfs", "/tmp", "--tmpfs", "/run", "--unshare-pid",
+            "--bind", salida, "/salida",
+            "--uid", "0", "--gid", "0",
+            "--setenv", "PATH", "/usr/bin:/bin:/usr/sbin:/sbin",
+            "--setenv", "XDG_RUNTIME_DIR", "/run", "--setenv", "HOME", "/root",
+            "--setenv", "XDG_DATA_DIRS", "/usr/share", "--setenv", "MOZ_HEADLESS", "1",
+            "--setenv", "MOZ_DISABLE_CONTENT_SANDBOX", "1",
+            "--setenv", "MOZ_DISABLE_RDD_SANDBOX", "1",
+            "sh", "-c", guion,
+        ], check=False)
+
+        log = open(os.path.join(salida, "consola.log"), errors="replace").read()
+        lineas = [l for l in log.splitlines() if l.startswith("INICIO-TEST")]
+        for l in lineas:
+            print("   " + l)
+        if not lineas:
+            shutil.copy(os.path.join(salida, "consola.log"),
+                        os.path.join(ROOT, "work", "atuq-inicio.log"))
+            fatal("la sonda no dijo nada — ¿no se instaló? log en work/atuq-inicio.log")
+
+        def valor(clave):
+            for l in lineas:
+                if l.startswith(f"INICIO-TEST {clave} "):
+                    return l.split(" ", 2)[2]
+            return None
+
+        home, newtab = valor("HOMEPAGE"), valor("NEWTAB")
+        nuestras = [v for v in (home, newtab) if v and "inicio.html" in v]
+
+        if negativo:
+            if nuestras:
+                fatal("SIN el XPI de inicio las páginas siguen siendo las nuestras"
+                      " — esta prueba no distingue nada")
+            print("✓ control negativo: sin el XPI son las de Firefox (la prueba SÍ lo vería)")
+            return 0
+        if len(nuestras) != 2:
+            shutil.copy(os.path.join(salida, "consola.log"),
+                        os.path.join(ROOT, "work", "atuq-inicio.log"))
+            fatal(f"la página de inicio y/o la pestaña nueva NO son de atuq"
+                  f" (homepage={home} newtab={newtab}) — log en work/atuq-inicio.log")
+        print("✓ la página de inicio y la pestaña nueva son las de atuq")
+        return 0
+    finally:
+        shutil.rmtree(tmp, ignore_errors=True)
+
+
+sys.exit(main())
diff --git a/scripts/test-atuq-nativo.py b/scripts/test-atuq-nativo.py
index ead25aab..12b4a165 100644
--- a/scripts/test-atuq-nativo.py
+++ b/scripts/test-atuq-nativo.py
@@ -96,6 +96,19 @@ def fatal(msg):
 def artefacto(receta, nombre):
     """El artefacto VIGENTE, por `hammer hash` y nunca por glob: con dos en el store, `ls | head`
     es una ruleta y se puede estar probando el de ayer."""
+    # ⚠ ESCOTILLA `ATUQ_DIR`, y con su aviso puesto. El corpus es compartido: otro frente puede
+    # mover una receta de arriba —el 2026-09-07 el perfil PGO v2 re-hasheó `firefox` y con él
+    # `atuq`— y entonces el artefacto VIGENTE no existe en ningún store hasta que alguien pague un
+    # build de horas. Sin escotilla, ninguna prueba de atuq se puede correr en esa ventana. Con
+    # escotilla, se puede correr contra un artefacto viejo A SABIENDAS: por eso grita, y por eso el
+    # resultado no se puede citar como «atuq de hoy pasa».
+    forzado = os.environ.get("ATUQ_DIR")
+    if forzado and nombre == "atuq":
+        if not os.path.isdir(forzado):
+            fatal(f"ATUQ_DIR={forzado} no existe")
+        print(f"⚠ ATUQ_DIR: se prueba {os.path.basename(forzado)}, que NO es necesariamente el vigente")
+        return forzado
+
     r = subprocess.run([HAMMER, "--store", STORE, "hash", f"recipes/{receta}.toml"],
                        cwd=ROOT, capture_output=True, text=True)
     if r.returncode != 0: