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
184 lines
8.2 KiB
Python
184 lines
8.2 KiB
Python
#!/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())
|