atuq §6.3: la búsqueda semántica, VERDE en la jaula — y la barra lateral tiene las dos preguntas

Los cuatro guardianes pasan sobre los artefactos sellados (atuq bfc14c92, puriy-costura 3f31233e,
ia-modelo-embeddings 2c0c4258):

  semántico    0.6252 gato / 0.2471 red / 0.0973 pan  → y la otra pregunta gana la otra página
  control      «no hay modelo de embeddings en …» y NINGÚN orden inventado
  ia           el modelo contesta y el motor se va con el navegador (0 vivos)
  foco         los tres estados, y el estado intacto tras la sesión

La barra lateral ahora tiene dos botones: «Al modelo» (genera texto) y «A mis páginas» (ordena lo que
ya leíste). No se mezclan a propósito — una inventa y la otra recuerda, y juntas sería imposible
saber cuál contestó. Los resultados van EN ORDEN y sin porcentaje: el puntaje es un coseno y leerlo
como «85 % de acierto» sería inventarle un significado.

⚠ Y el guardián nació midiendo NADA: metía las tres páginas en `<iframe>` y archivó cero, porque el
§6.3 ignora lo que no es marco principal. Encadenadas como navegación de verdad entran las tres; y
las páginas de tránsito van sin texto visible para que el archivo las descarte y la evidencia no
liste coincidencias sin título.

Dos cosas más del camino, las dos medidas: el `cp` del install buscaba el nombre de upstream y el tar
—nuestro— lleva el fichero con nombre corto; y el build murió dos veces por DISCO LLENO (0 bytes en
/mnt/vvv), no por el lock. `scripts/poda-fuentes.sh --horas 6` liberó 4,6 G, que es exactamente para
lo que existe.
This commit is contained in:
Sergio
2026-09-12 04:25:35 +00:00
parent 097579546f
commit 4a28013316
9 changed files with 156 additions and 27 deletions
+49 -5
View File
@@ -87,15 +87,26 @@ class Servidor(http.server.SimpleHTTPRequestHandler):
ruta = self.path.split("?")[0]
if ruta in PAGINAS:
titulo, texto = PAGINAS[ruta]
# Cada página lleva a la siguiente: tres cargas de PRIMER NIVEL con un solo arranque.
orden = list(PAGINAS)
i = orden.index(ruta)
siguiente = orden[i + 1] if i + 1 < len(orden) else "/fin.html"
datos = (f'<!doctype html><html><head><meta charset="utf-8"><title>{titulo}</title>'
f'<meta http-equiv="refresh" content="3;url={siguiente}">'
f"</head><body><h1>{titulo}</h1><p>{texto}</p></body></html>").encode()
elif ruta == "/ir.html":
# Una página que pasea por las tres: el guión de contenido archiva cada una al cargar.
saltos = "".join(f'<iframe src="{r}" width="10" height="10"></iframe>' for r in PAGINAS)
datos = (f'<!doctype html><html><head><meta charset="utf-8"></head><body>{saltos}'
"</body></html>").encode()
# ⚠ La primera versión de esto metía las tres páginas en `<iframe>` y NO archivaba
# ninguna: el archivo ignora a propósito lo que no es el marco principal (un iframe de
# publicidad no es una página que alguien leyó). Ahora se ENCADENAN como navegación de
# verdad, que es lo que hace una persona.
datos = (b'<!doctype html><html><head><meta charset="utf-8">'
b'<meta http-equiv="refresh" content="1;url=/gato.html">'
# Sin texto visible: el archivo descarta lo que no tiene nada que buscar, así
# que esta página de tránsito no ensucia la evidencia con una entrada sin título.
b"</head><body></body></html>")
else:
datos = b"<html><body>nada</body></html>"
# Igual que `/ir.html`: sin texto visible, para que el final de la cadena no se archive.
datos = b"<html><body></body></html>"
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(datos)))
@@ -192,6 +203,8 @@ def main():
correr(capas if not negativo else [atuq, costura, llama], salida, guion)
log = open(os.path.join(salida, "consola.log"), errors="replace").read()
base = next((l[len("IA BASE "):].strip() for l in log.splitlines()
if l.startswith("IA BASE ")), None)
lineas = [l[len("ARCHIVO "):] for l in log.splitlines() if l.startswith("ARCHIVO ")]
archivadas = [l for l in lineas if l.startswith("ARCHIVADA ")]
for l in archivadas:
@@ -230,9 +243,40 @@ def main():
fatal(f"«{q}» puso primera a {m[0]['page']['url']} y esperaba {esperada}"
" — el orden no sigue al SIGNIFICADO")
# ── Y LA BARRA LATERAL, que es por donde lo usa una persona ──────────────────────
# Lo de arriba mide el host; esto mide que la extensión llegue hasta él y muestre el
# orden. Sin esta mitad, la función existiría y no habría cómo usarla.
if not base:
fatal("la extensión de IA no dijo su URL base: no puedo abrir el panel")
q, esperada = PREGUNTAS[0]
panel = f"{base}panel.html?p={q.replace(' ', '%20').replace('¿', '%C2%BF')}"
guion2 = f"""
mkdir -p /root /salida/hogar
[ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id
export HOME=/salida/hogar
timeout 90 /usr/bin/atuq --profile /salida/perfil --no-remote --headless '{panel}' \
> /salida/panel.log 2>&1
exit 0
"""
correr(capas, salida, guion2)
plog = open(os.path.join(salida, "panel.log"), errors="replace").read()
coincidencias = [l[len("IA COINCIDENCIA "):] for l in plog.splitlines()
if l.startswith("IA COINCIDENCIA ")]
print("\n por la barra lateral:")
for c in coincidencias[:4]:
print(" " + c)
if not coincidencias:
destino = os.path.join(ROOT, "work", "atuq-panel-sem.log")
os.makedirs(os.path.dirname(destino), exist_ok=True)
shutil.copy(os.path.join(salida, "panel.log"), destino)
fatal(f"el panel no mostró ninguna coincidencia — log en {destino}")
if esperada not in coincidencias[0]:
fatal(f"el panel puso primera a «{coincidencias[0]}» y esperaba {esperada}")
print("\n✓ el archivo contesta por PARECIDO: cada pregunta pone primera a su página")
print("✓ y son dos preguntas con ganadores distintos — un ranking constante no pasaría")
print("✓ todo sin navegador: el archivo quedó en disco y se consulta por el cable")
print("✓ y por la barra lateral también: la extensión llega al host y muestra el orden")
return 0
finally:
servidor.shutdown()