tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

cierres §4: CVE por grafo — frontera mínima de rebuild (dirty_cone + topo_order)

`scripts/affected.py <pkg> [--agenda] [--sellados]`. Un CVE no obliga a
reconstruir el catálogo: obliga a reconstruir el CONO de lo que depende del
paquete, y nada más. El grafo declarado ya lo sabe.

  CVE en zlib  → 65 de 761 recetas (8%) — el resto NO se toca
  CVE en expat → 20 de 761 (2%), las 20 selladas = el trabajo real
                 agenda: fontconfig → fcft → cairo → pango → dbus → wayland…

Es el `dirty_cone` de `dataflow` (crate no_std de tawasuyu) + `topo_order` como
agenda (una dep antes que su consumidor), calculados sobre las deps declaradas.
`--sellados` separa lo que hay que reconstruir de verdad (tiene artefacto) de lo
que no existe aún.

CAE SOLO DEL INVARIANTE: las deps están declaradas por hash ⇒ el cono es
computable y EXACTO. Una distro sin clausura declarada tiene que adivinar o
reconstruir todo por las dudas. Ésa es la tesis del SDD 17.

Honestidad grabada en el propio output: la frontera es exacta respecto de lo
DECLARADO. Lo que una receta usa sin declarar queda FUERA del cono — y ese es el
agujero que harkaq cierra (SDD 16). La campaña de esta semana declaró ~84 deps
que el kernel midió y nadie había escrito: sin eso, este cono habría sido
optimista. **El cono es tan bueno como la clausura**, y por eso los dos frentes
se necesitan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

4374d2b5918bb4454ed1d36d710fddfb7c613ed7 · por sergio · (hace 3 meses) · padre 8ff8c1b82 · ver el árbol en este commit

Delta semántico · 4

scripts/affected.py

1 archivos · +114 −0

diff --git a/scripts/affected.py b/scripts/affected.py
new file mode 100755
index 00000000..04603d81
--- /dev/null
+++ b/scripts/affected.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+"""affected — frontera MÍNIMA de rebuild ante un CVE (SDD 17 §4).
+
+    affected.py zlib                 # ¿qué hay que reconstruir si zlib tiene un CVE?
+    affected.py zlib --agenda        # + el orden topológico (qué primero)
+    affected.py zlib --sellados       # sólo lo que YA está en el store (el trabajo real)
+
+Un CVE en `zlib` no obliga a reconstruir el catálogo: obliga a reconstruir **el cono de lo que
+depende de zlib**, y nada más. El grafo de dependencias declarado ya lo sabe — es el `dirty_cone`
+de `dataflow` (crate no_std de tawasuyu), y el `topo_order` es la agenda.
+
+Que esto sea exacto (y no "reconstruí todo por las dudas") **cae solo del invariante**: las deps
+están declaradas por hash, así que el cono es computable. Un distro sin clausura declarada tiene
+que adivinar.
+
+Nota honesta: la frontera es exacta respecto de **lo declarado**. Si una receta usa algo sin
+declararlo, queda fuera del cono — y ese es justo el agujero que harkaq cierra (SDD 16): la
+campaña de esta semana declaró ~84 deps que el kernel midió y nadie había escrito. **El cono es
+tan bueno como la clausura**, y por eso los dos frentes se necesitan.
+"""
+import collections
+import glob
+import os
+import re
+import sys
+
+
+def grafo():
+    """(deps, rev): receta → sus deps declaradas, y su inverso."""
+    deps = collections.defaultdict(set)
+    for f in glob.glob("recipes/*.toml"):
+        n = os.path.basename(f)[:-5]
+        s = open(f, errors="ignore").read()
+        m = re.search(r"\[deps\](.*?)(\n\[|\Z)", s, re.S)
+        if m:
+            for d in re.findall(r'"([^"]+)"', m.group(1)):
+                deps[n].add(d)
+    rev = collections.defaultdict(set)
+    for n, ds in deps.items():
+        for d in ds:
+            rev[d].add(n)
+    return deps, rev
+
+
+def cono(rev, semilla):
+    """dirty_cone: todo lo que depende TRANSITIVAMENTE de `semilla`. La frontera de rebuild."""
+    vistos, frontera = set(), [semilla]
+    while frontera:
+        x = frontera.pop()
+        for c in rev.get(x, ()):
+            if c not in vistos:
+                vistos.add(c)
+                frontera.append(c)
+    return vistos
+
+
+def agenda(deps, afectadas):
+    """topo_order dentro del cono: qué reconstruir primero (una dep antes que su consumidor)."""
+    pend = {n: {d for d in deps.get(n, ()) if d in afectadas} for n in afectadas}
+    orden, listos = [], [n for n, p in pend.items() if not p]
+    while listos:
+        n = listos.pop()
+        orden.append(n)
+        for m in afectadas:
+            if n in pend[m]:
+                pend[m].discard(n)
+                if not pend[m]:
+                    listos.append(m)
+    ciclo = [n for n in afectadas if n not in orden]
+    return orden, ciclo
+
+
+def main():
+    args = [a for a in sys.argv[1:] if not a.startswith("--")]
+    if not args:
+        print(__doc__.split("\n")[2].strip(), file=sys.stderr)
+        return 2
+    pkg = args[0]
+    if not os.path.exists(f"recipes/{pkg}.toml"):
+        print(f"no existe recipes/{pkg}.toml", file=sys.stderr)
+        return 2
+
+    deps, rev = grafo()
+    afectadas = cono(rev, pkg)
+    total = len(glob.glob("recipes/*.toml"))
+
+    print(f"══ CVE en «{pkg}» — frontera mínima de rebuild")
+    print(f"   {len(afectadas)} de {total} recetas afectadas ({len(afectadas)*100//max(total,1)}%)"
+          f" — el resto NO se toca")
+
+    if "--sellados" in sys.argv or "--agenda" in sys.argv:
+        sell = {n for n in afectadas if glob.glob(f"store/*-{n}")}
+        print(f"   de ésas, {len(sell)} ya están selladas en el store ⇒ ése es el trabajo real")
+
+    if "--agenda" in sys.argv:
+        orden, ciclo = agenda(deps, afectadas)
+        print("\n   ── agenda (topo_order: una dep antes que su consumidor):")
+        for i, n in enumerate(orden, 1):
+            marca = "●" if glob.glob(f"store/*-{n}") else "○"
+            print(f"      {i:3}. {marca} {n}")
+        if ciclo:
+            print(f"   ⚠ {len(ciclo)} en ciclo (no ordenables): {', '.join(sorted(ciclo))}")
+        print("      ● = sellado (hay que reconstruir)   ○ = sin artefacto")
+    else:
+        for n in sorted(afectadas):
+            print(f"      {n}")
+
+    print("\n   La frontera es exacta respecto de lo DECLARADO. Lo no declarado queda fuera del"
+          "\n   cono — es el agujero que harkaq cierra (SDD 16).")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())