gioser

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

⎇ 21381b598fbea84afcda61f7ec2b845b93eebc73 scripts/seed-gnome.py Python · 154 líneas · 9.2 KB Historia Quién tocó qué crudo

takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4 476168bb0 · Sergio ·

1#!/usr/bin/env python3
2# seed-gnome.py — siembra el SUBTREE transitivo de GNOME desde metadata de nixpkgs (NO construye con
3# nix, ADR 0004: sólo lee deps). Corre `sembrar_cierre` de seed-graph sobre las raíces de una sesión
4# GNOME y clasifica el cierre:
5#   conocida  → ya hay receta en el corpus (cache-hit potencial)
6#   frontera  → nixpkgs lo pide y takana NO tiene receta → HAY QUE AUTORAR (esto es el trabajo real)
7#   nix-ismo/lab → ruido de nixpkgs o lo provee el sandbox (se descarta)
8#
9# Cada `frontera` es una HIPÓTESIS a clasificar/autorar por un humano (o un subagente-por-receta), NO
10# se promueve a receta automáticamente. Salida durable a docs/state/seed-gnome.json.
11import json, sys, pathlib, importlib.util
12# el guion en `seed-graph.py` impide `import seed_graph`: lo cargo por ruta.
13_spec = importlib.util.spec_from_file_location(
14    "seed_graph", str(pathlib.Path(__file__).parent / "seed-graph.py"))
15sg = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(sg)
16
17ROOT = pathlib.Path(__file__).resolve().parent.parent
18OUT = ROOT / "docs/state/seed-gnome.json"
19
20# Raíces de una sesión GNOME usable: el compositor (mutter), el shell y su runtime JS (gjs), la
21# sesión y sus daemons, el display manager, el portal, y las apps mínimas (files, terminal, ajustes).
22# El cierre transitivo trae el resto (mozjs bajo gjs, gcr/libsecret bajo el keyring, etc.).
23RAICES = [
24    "mutter", "gnome-shell", "gjs", "gnome-session", "gnome-settings-daemon",
25    "gsettings-desktop-schemas", "gnome-desktop", "gnome-menus", "gnome-keyring",
26    "gdm", "xdg-desktop-portal", "xdg-desktop-portal-gnome", "libadwaita",
27    "nautilus", "gnome-console", "gnome-control-center", "gnome-text-editor",
28]
29
30MINIMA = [  # lo que una sesión GNOME necesita para BOOTEAR a un shell usable (sin apps)
31    "mutter", "gnome-shell", "gjs", "gnome-session", "gnome-settings-daemon",
32    "gsettings-desktop-schemas", "gnome-desktop", "gdm", "xdg-desktop-portal-gnome",
33]
34
35# ── LA OPTIMIZACIÓN (el "yupana de gnome", no un sembrador crudo). El cierre de metadata de nixpkgs
36# SOBREESTIMA ~5-10× la frontera real de takana, porque declara buildInputs de features MÁXIMAS. Estas
37# tablas son HIPÓTESIS de triage (NO verdad — la verdad emerge autorando feature-minimal), para que el
38# reporte no mienta con "465 recetas". Cada una vuelve legible por qué un candidato probablemente NO es
39# hueco. Clasificar a mano al autorar; mover entre cubetas es esperable.
40SUSTITUCION = {  # hammer YA tiene equivalente → mapeo, no hueco
41    "systemd", "systemd-minimal", "systemd-minimal-libs",  # → elogind
42    "libpulseaudio",   # → pipewire (libpulse compat)
43    "networkmanager",  # → iwd / systemd-networkd (a elegir)
44}
45TOOLING = {  # generadores de docs / build-glue: el lab los provee o se deshabilitan
46    "vala", "gi-docgen", "gtk-doc", "docutils", "doxygen", "texinfo", "autoconf", "automake",
47    "autoconf-archive", "xmlto", "nasm", "yasm", "cmocka", "gtest", "bash-completion",
48    "desktop-file-utils", "gettext", "itstool", "sassc", "meson", "ninja", "help2man", "gperf",
49    "appstream", "appstream-glib", "asciidoc", "docbook-xsl", "docbook-xml", "docbook-sgml",
50    "docbook5", "perl", "python3", "gobject-introspection", "sphinx", "graphviz", "groff", "swig",
51    "dejagnu", "expect", "valgrind", "gdb", "man-db", "po4a", "ragel", "byacc", "gengetopt",
52    "validate-pkg-config", "prune-libtool-files",
53}
54# subárboles de features/apps que una sesión mínima NO necesita (defer): media, impresión, red,
55# almacenamiento, TPM, escáner, runtimes de scripting arrastrados por deps opcionales.
56OPCIONAL_PREF = (
57    "ffmpeg", "gst-plugins-bad", "gst-plugins-ugly", "gst-libav", "x264", "x265", "svt-av1",
58    "dav1d", "libaom", "libvpx", "fdk-aac", "libmad", "libmpg123", "mpg123", "opusfile", "wavpack",
59    "libtheora", "vorbis", "zimg", "vid.stab", "libass", "srt", "zvbi", "sbc", "ldacbt", "liblc3",
60    "samba", "talloc", "tevent", "tdb", "ldb", "libnfs", "libiscsi", "cyrus-sasl", "net-snmp",
61    "unbound", "cups", "sane", "colord", "argyllcms", "bluez", "freerdp", "evolution",
62    "gnome-online-accounts", "gnome-remote-desktop", "webkitgtk", "gnome-clocks", "flatpak",
63    "ostree", "gvfs", "openjdk", "ruby", "lua", "tcl", "tpm2", "swtpm", "libtpms", "cryptsetup",
64    "lvm2", "libnvme", "libndctl", "mariadb", "psqlodbc", "unixodbc", "openldap", "krb5",
65    "sphinxcontrib", "geoclue", "libcamera", "roc-toolkit", "exiv2", "poppler",
66)
67
68def _cubeta(dep):
69    if dep in SUSTITUCION: return "sustitucion"
70    if dep in TOOLING:     return "tooling"
71    if any(t in dep for t in OPCIONAL_PREF): return "opcional"
72    return "espinazo"
73
74def main():
75    recs = sg.recetas()
76    raices = MINIMA if (len(sys.argv) > 1 and sys.argv[1] == "--minima") else RAICES
77    print(f"== raíces GNOME: {len(raices)}", file=sys.stderr)
78    grafo = sg.sembrar_cierre(raices, recs, prof_max=6, tope=4000)
79    out_path = ROOT / ("docs/state/seed-gnome-minima.json" if raices is MINIMA else "docs/state/seed-gnome.json")
80    # clasificar TODO el cierre
81    clases = {"conocida": [], "frontera": [], "nix-ismo": [], "lab": [], "sin-attr": []}
82    for n, aristas in grafo.items():
83        if aristas is None:
84            clases["sin-attr"].append(n); continue
85    # nodos del cierre = todo lo referido como dep, clasificado por su presencia
86    todos_deps = {}
87    for n, aristas in grafo.items():
88        for a in (aristas or []):
89            d = a["dep"]
90            # el nodo se clasifica por su MEJOR evidencia (conocida > frontera)
91            prev = todos_deps.get(d)
92            rank = {"conocida": 3, "lab": 2, "nix-ismo": 1, "frontera": 0}
93            if prev is None or rank[a["clase"]] > rank[prev["clase"]]:
94                todos_deps[d] = a
95    conocidas = sorted(d for d, a in todos_deps.items() if a["clase"] == "conocida")
96    frontera  = sorted(d for d, a in todos_deps.items() if a["clase"] == "frontera")
97    nixismos  = sorted(d for d, a in todos_deps.items() if a["clase"] == "nix-ismo")
98    lab       = sorted(d for d, a in todos_deps.items() if a["clase"] == "lab")
99    sin_attr  = sorted(clases["sin-attr"])
100
101    # ¿cuántas veces piden cada frontera? (prioridad de autoría)
102    demanda = {}
103    for n, aristas in grafo.items():
104        for a in (aristas or []):
105            if a["clase"] == "frontera":
106                demanda.setdefault(a["dep"], []).append(n)
107
108    print(f"\n== SUBTREE GNOME (cierre transitivo, {len(grafo)} nodos visitados)")
109    print(f"   ya tenemos receta (conocida): {len(conocidas)}")
110    print(f"   FRONTERA a autorar:           {len(frontera)}")
111    print(f"   nix-ismos (ruido):            {len(nixismos)}")
112    print(f"   provisto por el lab:          {len(lab)}")
113    print(f"   sin attr en nixpkgs:          {len(sin_attr)}"
114          + (f" ({' '.join(sin_attr[:8])}…)" if sin_attr else ""))
115    # ── TRIAGE (hipótesis): parte la frontera cruda en cubetas para no mentir con el conteo bruto.
116    cubetas = {"sustitucion": [], "tooling": [], "opcional": [], "espinazo": []}
117    for d in frontera:
118        cubetas[_cubeta(d)].append(d)
119    espinazo = sorted(cubetas["espinazo"], key=lambda k: (-len(demanda.get(k, [])), k))
120    # keystone / palo largo: gjs bloquea gnome-shell, y gjs cuelga de spidermonkey(mozjs).
121    palo_largo = [d for d in ("spidermonkey", "gjs", "mozjs") if d in frontera]
122
123    print(f"\n== TRIAGE de la frontera cruda (HIPÓTESIS — el cierre de nixpkgs sobreestima ~5-10×):")
124    print(f"   sustitución (systemd→elogind…): {len(cubetas['sustitucion'])}")
125    print(f"   tooling/docs (lab o disable):    {len(cubetas['tooling'])}")
126    print(f"   opcional (media/print/red/…):    {len(cubetas['opcional'])}")
127    print(f"   ⇒ ESPINAZO candidato a autorar:  {len(espinazo)}  (SIGUE siendo cota ALTA)")
128    print(f"   ⚠ palo largo / keystone:         {' → '.join(palo_largo) or '—'}  (bloquea gnome-shell)")
129    print(f"\n== espinazo por demanda — top 30 (hipótesis, clasificar al autorar):")
130    for d in espinazo[:30]:
131        print(f"   {len(demanda.get(d, [])):3}×  {d}")
132
133    doc = dict(
134        schema="hammer-seed-gnome/2",
135        fuente="nixpkgs metadata (NO se construye con nix — ADR 0004)",
136        advertencia=("El cierre de metadata SOBREESTIMA ~5-10× la frontera real de hammer (buildInputs "
137                     "de features máximas). Las cubetas son HIPÓTESIS, no verdad: la frontera real "
138                     "emerge autorando TOP-DOWN feature-minimal. NO promover a receta automáticamente."),
139        raices=raices,
140        resumen=dict(visitados=len(grafo), conocida=len(conocidas), frontera_cruda=len(frontera),
141                     nix_ismo=len(nixismos), lab=len(lab), sin_attr=len(sin_attr),
142                     sustitucion=len(cubetas["sustitucion"]), tooling=len(cubetas["tooling"]),
143                     opcional=len(cubetas["opcional"]), espinazo=len(espinazo)),
144        palo_largo=palo_largo,
145        espinazo=espinazo, triage=cubetas,
146        conocida=conocidas, frontera=frontera, nix_ismo=nixismos, lab=lab, sin_attr=sin_attr,
147        demanda={d: sorted(set(q)) for d, q in sorted(demanda.items(), key=lambda kv:(-len(kv[1]),kv[0]))},
148    )
149    out_path.write_text(json.dumps(doc, indent=1, ensure_ascii=False) + "\n")
150    print(f"\n   → {out_path.relative_to(ROOT)}")
151    return 0
152
153if __name__ == "__main__":
154    sys.exit(main())