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…
⎇ a1ccce2b65dc99a65189455719c45bdae3b08976
scripts/affected.py
Ñawpa willay
Pi imata llamkarqan
ch'uya
cierres §4: CVE por grafo — frontera mínima de rebuild (dirty_cone + topo_order)
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())