khipu: el motor de dependencias inversas que cruza TODAS las colas (y arregla la mentira de perfiles)
El 2026-07-22 un cambio a libdrm (default_library=both, para gtk4) invalidó 118
recetas KDE. El radio se midió contra recipes/*.toml y no contra
recipes/incoming-kde/ ⇒ 8 en vez de 118. Y build-state.py sin --kde cometía el
MISMO error: cargaba sólo el corpus, y su campo `perfiles` AFIRMABA que tocar
libdrm sólo afectaba a escritorio-mirada. Una mentira con confianza.
Causa estructural: las dependencias INVERSAS son un hecho del disco entero, no
de la vista que uno cargó. khipu.py las calcula SIEMPRE sobre todas las colas,
con resolución sibling-first (el nodo se identifica por (cola, nombre), porque
corpus/fontconfig y incoming-kde/fontconfig son nudos distintos).
QUÉ NODOS PUNTÚO es decisión de vista. QUIÉN ME CONSUME es un hecho.
khipu es la PUERTA ÚNICA de la metodología: radio/perfiles nativos (cruzan todo
el repo) + delega estado/drenar/triaje/frontera/objetivo a sus órganos.
khipu radio libdrm → 126 directos, 132 transitivos, [escritorio-kde,
escritorio-mirada], 11 sellados que caen. El número
honesto que habría frenado el cambio.
build-state.py ahora saca `perfiles` y el nuevo `dependientes_total` de khipu
(grafo entero), no de los nodos que cargó. Verificado: el grafo por defecto ya
atribuye libdrm a escritorio-kde; sin_perfil bajó 674→666 (8 recetas del corpus
que sólo KDE consume, ahora bien atribuidas). --check sigue en 0, grafo cierra.
Repo-wide por diseño, no KDE-céntrico: zlib toca las 4 imágenes (233 transitivos),
make 313.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-10
@@ -40,6 +40,7 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import targets as targets_mod
|
||||
import khipu # el motor de dependencias INVERSAS: cruza TODAS las colas (ver por qué en su cabecera)
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
os.chdir(ROOT)
|
||||
@@ -220,18 +221,29 @@ def main():
|
||||
r["unblocks"] = sum(1 for m, rm in recs.items()
|
||||
if rm["state"] in DEBT_STATES and n in rm.get("deps", []))
|
||||
|
||||
# MEMBRESÍA DE PERFIL: qué imágenes alcanzan cada nodo. `perfiles: []` = no lo necesita ninguna
|
||||
# imagen declarada (catálogo, no distro) — el dato incómodo que hasta ahora nadie medía.
|
||||
for r in recs.values():
|
||||
r["perfiles"] = []
|
||||
# MEMBRESÍA DE PERFIL y DEPENDIENTES INVERSOS: los dos se calculan sobre el GRAFO ENTERO (todas
|
||||
# las colas del disco), NO sobre los nodos que esta vista cargó. Es la corrección del bug de
|
||||
# libdrm: en la corrida del corpus, `perfiles` llegaba a decir `['escritorio-mirada']` sobre un
|
||||
# paquete que sostiene KDE entero, porque no veía las recetas de incoming-kde. khipu las ve
|
||||
# siempre y resuelve sibling-first ⇒ el nodo se identifica por (cola, nombre). Ver khipu.py.
|
||||
todas = khipu.cargar_colas()
|
||||
memb = khipu.membresia(todas, cargar_perfiles({q for q in todas})) # TODOS los perfiles, no sólo los de esta vista
|
||||
rev = khipu.dependientes(todas)
|
||||
for n, r in recs.items():
|
||||
clave = (r.get("queue", "corpus"), n)
|
||||
r["perfiles"] = sorted(memb.get(clave, ()))
|
||||
# Dependientes inversos DEL GRAFO ENTERO: cuántas recetas (de cualquier cola) me consumen. Es
|
||||
# el radio real de un re-hash. `unblocks` (abajo) es local a la deuda de esta vista; este no.
|
||||
r["dependientes_total"] = len(rev.get(clave, ()))
|
||||
sin_perfil = sum(1 for r in recs.values() if not r["perfiles"])
|
||||
|
||||
# `by_profile` (el "cuánto falta" por imagen) SÍ es local: sólo cuenta estados de nodos que esta
|
||||
# vista cargó ⇒ se limita a los perfiles cuya cola está cargada (los demás darían conteos falsos).
|
||||
by_profile = {}
|
||||
for pn, p in sorted(perfiles.items()):
|
||||
cl = clausura(p["raices"], recs)
|
||||
for n in sorted(cl):
|
||||
recs[n]["perfiles"].append(pn)
|
||||
by_profile[pn] = dict(raices=len(p["raices"]), clausura=len(cl),
|
||||
**Counter(recs[n]["state"] for n in cl))
|
||||
sin_perfil = sum(1 for r in recs.values() if not r["perfiles"])
|
||||
miembros = [n for n, r in recs.items() if pn in r["perfiles"]]
|
||||
by_profile[pn] = dict(raices=len(p["raices"]), clausura=len(miembros),
|
||||
**Counter(recs[n]["state"] for n in miembros))
|
||||
|
||||
by_state = Counter(r["state"] for r in recs.values())
|
||||
by_cls = Counter(r["cls"] for r in recs.values() if "cls" in r)
|
||||
|
||||
Reference in New Issue
Block a user