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 1184Shell 70Rust 62Markdown 501503 Definiciones

⎇ ce2668598e4d8b60e5e070a0e422cd1dd12f0033 scripts/affected.py Python · 114 líneas · 4.3 KB Historia Quién tocó qué crudo

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

1#!/usr/bin/env python3
2"""affected — frontera MÍNIMA de rebuild ante un CVE (SDD 17 §4).
3
4    affected.py zlib                 # ¿qué hay que reconstruir si zlib tiene un CVE?
5    affected.py zlib --agenda        # + el orden topológico (qué primero)
6    affected.py zlib --sellados       # sólo lo que YA está en el store (el trabajo real)
7
8Un CVE en `zlib` no obliga a reconstruir el catálogo: obliga a reconstruir **el cono de lo que
9depende de zlib**, y nada más. El grafo de dependencias declarado ya lo sabe — es el `dirty_cone`
10de `dataflow` (crate no_std de tawasuyu), y el `topo_order` es la agenda.
11
12Que esto sea exacto (y no "reconstruí todo por las dudas") **cae solo del invariante**: las deps
13están declaradas por hash, así que el cono es computable. Un distro sin clausura declarada tiene
14que adivinar.
15
16Nota honesta: la frontera es exacta respecto de **lo declarado**. Si una receta usa algo sin
17declararlo, queda fuera del cono — y ese es justo el agujero que harkaq cierra (SDD 16): la
18campaña de esta semana declaró ~84 deps que el kernel midió y nadie había escrito. **El cono es
19tan bueno como la clausura**, y por eso los dos frentes se necesitan.
20"""
21import collections
22import glob
23import os
24import re
25import sys
26
27
28def grafo():
29    """(deps, rev): receta → sus deps declaradas, y su inverso."""
30    deps = collections.defaultdict(set)
31    for f in glob.glob("recipes/*.toml"):
32        n = os.path.basename(f)[:-5]
33        s = open(f, errors="ignore").read()
34        m = re.search(r"\[deps\](.*?)(\n\[|\Z)", s, re.S)
35        if m:
36            for d in re.findall(r'"([^"]+)"', m.group(1)):
37                deps[n].add(d)
38    rev = collections.defaultdict(set)
39    for n, ds in deps.items():
40        for d in ds:
41            rev[d].add(n)
42    return deps, rev
43
44
45def cono(rev, semilla):
46    """dirty_cone: todo lo que depende TRANSITIVAMENTE de `semilla`. La frontera de rebuild."""
47    vistos, frontera = set(), [semilla]
48    while frontera:
49        x = frontera.pop()
50        for c in rev.get(x, ()):
51            if c not in vistos:
52                vistos.add(c)
53                frontera.append(c)
54    return vistos
55
56
57def agenda(deps, afectadas):
58    """topo_order dentro del cono: qué reconstruir primero (una dep antes que su consumidor)."""
59    pend = {n: {d for d in deps.get(n, ()) if d in afectadas} for n in afectadas}
60    orden, listos = [], [n for n, p in pend.items() if not p]
61    while listos:
62        n = listos.pop()
63        orden.append(n)
64        for m in afectadas:
65            if n in pend[m]:
66                pend[m].discard(n)
67                if not pend[m]:
68                    listos.append(m)
69    ciclo = [n for n in afectadas if n not in orden]
70    return orden, ciclo
71
72
73def main():
74    args = [a for a in sys.argv[1:] if not a.startswith("--")]
75    if not args:
76        print(__doc__.split("\n")[2].strip(), file=sys.stderr)
77        return 2
78    pkg = args[0]
79    if not os.path.exists(f"recipes/{pkg}.toml"):
80        print(f"no existe recipes/{pkg}.toml", file=sys.stderr)
81        return 2
82
83    deps, rev = grafo()
84    afectadas = cono(rev, pkg)
85    total = len(glob.glob("recipes/*.toml"))
86
87    print(f"══ CVE en «{pkg}» — frontera mínima de rebuild")
88    print(f"   {len(afectadas)} de {total} recetas afectadas ({len(afectadas)*100//max(total,1)}%)"
89          f" — el resto NO se toca")
90
91    if "--sellados" in sys.argv or "--agenda" in sys.argv:
92        sell = {n for n in afectadas if glob.glob(f"store/*-{n}")}
93        print(f"   de ésas, {len(sell)} ya están selladas en el store ⇒ ése es el trabajo real")
94
95    if "--agenda" in sys.argv:
96        orden, ciclo = agenda(deps, afectadas)
97        print("\n   ── agenda (topo_order: una dep antes que su consumidor):")
98        for i, n in enumerate(orden, 1):
99            marca = "●" if glob.glob(f"store/*-{n}") else "○"
100            print(f"      {i:3}. {marca} {n}")
101        if ciclo:
102            print(f"   ⚠ {len(ciclo)} en ciclo (no ordenables): {', '.join(sorted(ciclo))}")
103        print("      ● = sellado (hay que reconstruir)   ○ = sin artefacto")
104    else:
105        for n in sorted(afectadas):
106            print(f"      {n}")
107
108    print("\n   La frontera es exacta respecto de lo DECLARADO. Lo no declarado queda fuera del"
109          "\n   cono — es el agujero que harkaq cierra (SDD 16).")
110    return 0
111
112
113if __name__ == "__main__":
114    sys.exit(main())