yupana: invariante (c) CANÓNICO — detector de duplicados por evidencia de fuente
`yupana duplicados` caza nudos que son el MISMO paquete bajo nombres/colas
distintos, agrupando por lo que BAJA (sha/url), no por el nombre. Clasifica:
colisión-fuente — mismo sha, familias de nombre DISTINTAS ⇒ casi seguro un
sha copiado por error. ACCIONABLE.
variante — mismo sha, misma familia (zlib/zlib-shared, mesa-*) ⇒
diversificación deliberada estático/shared, OK (14 vistas).
sombra — mismo nombre en >1 cola; redundante si versión+sha+deps
coinciden (una sobra), justificada si difieren.
HALLAZGO INMEDIATO — el detector encontró un bug real en su primera corrida:
itstool.toml dice versión 2.0.7 pero su url+sha apuntan al tarball de
gettext-tiny (29cc165e…). Construiría la fuente equivocada. radio itstool=1,
gettext-tiny=78 ⇒ el sha copiado es el de itstool.
Otras 3 colisiones son demos (adwaita-hello/libadwaita, sourceview-hello/
gtksourceview, radio 0) o subcomponentes (prison/prison-scanner) — a revisar,
no urgentes. Y 1 sombra redundante: incoming-kde/dbus es byte-idéntica a
corpus/dbus (mismo hash de artefacto) ⇒ los 114 consumidores KDE podrían
apuntar a corpus/dbus sin rebuild.
NO funde nada solo: reporta a docs/state/duplicados.json y deja el juicio al
humano (como el triaje). El radio de cada nudo dice cuál es el canónico (el de
mayor radio). Guardián extendido: test-yupana-radio.py falla si el detector
deja de ver colisiones de fuente (agrupar por nombre en vez de sha = invariante
c ciego).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,22 @@ def main():
|
||||
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
|
||||
|
||||
# 3) INVARIANTE (c): el detector de duplicados debe seguir viendo la fuente, no el nombre. La
|
||||
# identidad de un nudo es lo que BAJA (sha), no cómo se llama. Si esto se rompe (p.ej. alguien
|
||||
# agrupa por nombre), las colisiones de fuente —shas copiados por error— dejan de verse.
|
||||
recs_f = yupana._recetas_con_fuente()
|
||||
por_sha = {}
|
||||
for k, r in recs_f.items():
|
||||
if r["sha"]:
|
||||
por_sha.setdefault(r["sha"], set()).add(k[1]) # nombres por sha
|
||||
colis = {s: ns for s, ns in por_sha.items()
|
||||
if len({yupana._base(n) for n in ns}) > 1} # familias distintas, misma fuente
|
||||
if not colis:
|
||||
fallos.append("el detector de duplicados no ve NINGUNA colisión de fuente — "
|
||||
"¿se está agrupando por nombre en vez de por sha? (invariante c ciego)")
|
||||
|
||||
if fallos:
|
||||
print("✗ REGRESIÓN del radio cruzado:")
|
||||
print("✗ REGRESIÓN de yupana:")
|
||||
for f in fallos:
|
||||
print(" -", f)
|
||||
return 1
|
||||
@@ -47,6 +61,7 @@ def main():
|
||||
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()))}")
|
||||
print(f"✓ detector de duplicados por fuente: {len(colis)} colisiones vistas (invariante c activo)")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user