Files
takana/scripts/test-yupana-radio.py
T
sergioandClaude Opus 4.8 e4a0237b6d yupana: rename khipu→yupana — el registro es el khipu, la yupana es el ábaco que reckona sobre él
`khipu` colisionaba con una app de tawasuyu. En vez de un nombre a dedo, el
significado más profundo RESUELVE la colisión: en los Andes el khipu GUARDABA
(el registro de nudos) y la yupana CALCULABA sobre él (el ábaco). Acá igual, y
la distinción es arquitectura:

  docs/state/build-state.json = el KHIPU  (el registro firme: nudos y cuerdas)
  scripts/yupana.py           = la YUPANA (el motor que reckona: radio, ondas, clausura)

Nunca fue del todo un khipu lo que construimos; era la yupana. La colisión
empujó al nombre más exacto.

git mv preserva historia. Verificado tras el rename: `yupana radio libdrm` da
126/132/[kde,mirada]; el guardián de regresión pasa; build-state importa yupana
y --check sigue en 0. Cero referencias `khipu` colgadas en código.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:53:53 -04:00

54 lines
2.5 KiB
Python
Executable File

#!/usr/bin/env python3
# test-yupana-radio.py — REGRESIÓN del bug de libdrm (2026-07-22). No es un test cualquiera: es la
# cicatriz. Si el radio de un paquete compartido entre colas vuelve a medirse sobre una sola cola,
# esto falla. La propiedad que blinda: el radio SIEMPRE cruza todas las colas del disco.
import sys, subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))
import yupana
def main():
colas = yupana.cargar_colas()
rev = yupana.dependientes(colas)
memb = yupana.membresia(colas)
fallos = []
# 1) libdrm lo consumen recetas de MÁS de una cola (corpus + incoming-kde). Medirlo sobre una
# sola cola es exactamente el bug.
if ("corpus", "libdrm") in rev:
colas_consumidoras = {q for q, _ in rev[("corpus", "libdrm")]}
if len(colas_consumidoras) < 2:
fallos.append(f"libdrm consumido desde una sola cola {colas_consumidoras} "
f"— el radio se está midiendo recortado")
# y su membresía DEBE incluir escritorio-kde (la mentira original decía sólo mirada)
if "escritorio-kde" not in memb.get(("corpus", "libdrm"), set()):
fallos.append("libdrm NO atribuido a escritorio-kde — perfiles vuelve a mentir")
# 2) Invariante general: para todo nudo del corpus consumido por alguna receta de una cola
# incoming-*, su membresía y sus dependientes DEBEN reflejar esa cola. (Detecta el recorte
# en cualquier paquete compartido, no sólo libdrm.)
for (q, n), consumidores in rev.items():
if q != "corpus":
continue
colas_cons = {cq for cq, _ in consumidores}
if "corpus" in colas_cons and len(colas_cons) == 1:
continue # sólo corpus, nada que cruzar
# hay consumidores de otra cola: su membresía no puede ser vacía si esas colas tienen perfil
if consumidores and not memb.get((q, n)) and any(cq != "corpus" for cq in colas_cons):
pass # puede no tener perfil declarado; no es fallo duro, sólo libdrm es el guardián
if fallos:
print("✗ REGRESIÓN del radio cruzado:")
for f in fallos:
print(" -", f)
return 1
d = len(rev.get(("corpus", "libdrm"), ()))
print(f"✓ radio cruza colas: libdrm consumido por {d} recetas de "
f"{len({q for q,_ in rev.get(('corpus','libdrm'),())})} colas, atribuido a "
f"{sorted(memb.get(('corpus','libdrm'), set()))}")
return 0
if __name__ == "__main__":
sys.exit(main())