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…

fix(distro): el .zip SÍ se podía abrir — `libarchive` estaba sellado y sólo lo declaraba KDE

Mi censo marcaba «archivos comprimidos ajenos» 5/5 ausente, con este argumento: «no se puede abrir
un .zip, un .7z ni un .rar — `libzip` es una BIBLIOTECA, no el comando». Cierto para `libzip`, y
**falso como conclusión**: `libarchive` estaba SELLADO desde antes y trae `bsdunzip`, `bsdtar`,
`bsdcpio` y `bsdcat` — declarado únicamente en `escritorio-kde`, así que las otras nueve imágenes no
podían abrir un adjunto por un paquete ya construido.

MEDIDO ejecutándolo, con un zip deflate con subdirectorio, antes de escribir esto:

    bsdunzip -o x.zip          extrae y conserva el árbol                        ✓
    bsdtar -xf x.zip           ídem                                              ✓
    bsdtar -a -cf x.zip …      CREA un zip que python zipfile abre y verifica    ✓

⇒ `libarchive` entra a `cli`, o sea a los cuatro escritorios y a `servidor`. Clausura 10 de 10
verificada.

Y el censo gana `CUBIERTOS`, que es lo simétrico de `MUROS`: el renglón falta POR NOMBRE y la
capacidad está, medida. **No absuelve el hueco** —un script ajeno que invoque `unzip` sigue
fallando, y el censo hace bien en contarlo, porque mide equivalencia por nombre— pero cambia el
presupuesto de «no hay camino» a «faltan los alias», que es otra cosa y mucho más barata. Es la
misma figura que `agetty` cubriendo a `getty`. Sólo entra lo verificado EJECUTANDO, no lo que
upstream promete: 7z y rar quedan sin medir y sin afirmar.

De paso, lo medido sobre los métodos de entrada —el siguiente hueco de la lista— por si alguien lo
retoma: **no hay muro estructural.** `mirada-compositor` registra `TextInputManagerState` y
`InputMethodManagerState` (`setup.rs:600-602`), o sea `zwp_text_input_v3` y `zwp_input_method_v2`.
Y fcitx5 con `ENABLE_X11=Off` + `ENABLE_ENCHANT=Off` se quita cinco deps (xcb-imdkit,
xcb-util-keysyms, xcb-util-wm, libxkbfile, enchant2). Lo que queda bloqueando son dos promociones de
cola a corpus —`extra-cmake-modules` e `iso-codes`, hoy en `incoming-kde`, y una receta del corpus
no alcanza una cola hermana— más los motores por idioma, que son recetas aparte. Es una campaña de
media docena, no una receta, y está scopeada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

93f2d023d2ea59c00960e4687038a2719322a755 · by Sergio · (2 d ago) · parent 2a2fa49e6 · browse the tree at this commit

Semantic delta · 1

scripts/censo-distro.py

  • body changed fn main

3 files · +61 −14

diff --git a/docs/state/distro-huecos.json b/docs/state/distro-huecos.json
index fba81ad4..81346a66 100644
--- a/docs/state/distro-huecos.json
+++ b/docs/state/distro-huecos.json
@@ -3,31 +3,33 @@
   {
    "categoria": "escritura del mundo",
    "faltan": [
-    "noto-fonts-cjk",
-    "noto-fonts-emoji",
-    "noto-fonts",
     "noto-fonts-extra"
    ],
    "items": [
     {
-     "estado": "falta",
+     "estado": "tiene-y-viaja",
      "nombre": "noto-fonts-cjk",
      "parientes": []
     },
     {
-     "estado": "falta",
+     "estado": "tiene-y-viaja",
      "nombre": "noto-fonts-emoji",
      "parientes": []
     },
     {
-     "estado": "falta",
+     "estado": "tiene-y-viaja",
      "nombre": "noto-fonts",
      "parientes": []
     },
     {
      "estado": "falta",
      "nombre": "noto-fonts-extra",
-     "parientes": []
+     "parientes": [
+      "noto-fonts",
+      "noto-fonts-cjk",
+      "noto-fonts-emoji"
+     ],
+     "pista": "nombre parecido en el catálogo (verificar, NO es equivalencia)"
     }
    ],
    "porque": "las únicas fuentes del corpus son dejavu y su nerd: sin estas, la distro muestra cuadraditos en chino, japonés, coreano, árabe, hindi, tailandés y emoji"
@@ -404,11 +406,15 @@
    ],
    "items": [
     {
+     "cubierto_por": "libarchive",
+     "cubierto_porque": "bsdunzip -o x.zip extrae y conserva el árbol — probado con un zip deflate con subdirectorio el 2026-10-19",
      "estado": "falta",
      "nombre": "unzip",
      "parientes": []
     },
     {
+     "cubierto_por": "libarchive",
+     "cubierto_porque": "bsdtar -a -cf x.zip … crea un zip que python zipfile lee y verifica — probado el 2026-10-19",
      "estado": "falta",
      "nombre": "zip",
      "parientes": []
@@ -429,7 +435,7 @@
      "parientes": []
     }
    ],
-   "porque": "no se puede abrir un .zip, un .7z ni un .rar — un adjunto cualquiera queda cerrado. `libzip` es una BIBLIOTECA, no el comando"
+   "porque": "⚠ EL ARGUMENTO DE ESTA FILA ERA FALSO Y LO MEDÍ DESPUÉS: decía «no se puede abrir un .zip» porque `libzip` es una biblioteca y no el comando. Cierto para libzip — y falso como conclusión: `libarchive` estaba SELLADO y trae `bsdunzip`/`bsdtar`, verificados con un zip real en lectura Y escritura. Se declaró en `cli` el 2026-10-19. Lo que falta son los ALIAS (`unzip`, `zip`) para que un script ajeno que los invoque no falle, y 7z/rar sin medir"
   },
   {
    "categoria": "imprimir de verdad",
@@ -1365,12 +1371,12 @@
    "porque": "decisión abierta: son herramientas de operar OTRA cosa"
   }
  ],
- "en_alguna_imagen": 601,
- "recetas_alcanzables": 1375,
+ "en_alguna_imagen": 604,
+ "recetas_alcanzables": 1378,
  "resumen": {
-  "falta": 152,
+  "falta": 149,
   "tiene-sin-viajar": 3,
-  "tiene-y-viaja": 15
+  "tiene-y-viaja": 18
  },
  "schema": "takana-censo-distro/1"
 }
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 0b81645f..fd5911f6 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -407,6 +407,25 @@ paquetes = [
   # con razón también, porque mira el store LOCAL. Las dos fuentes eran ciertas y medían cosas
   # distintas, y yo leí la contradicción como «falta construirlo».
   "typst",
+  # ── ABRIR UN .ZIP, QUE CREÍAMOS QUE NO SE PODÍA (2026-10-19) ─────────────────────────────────
+  # `censo-distro.py` marcaba «archivos comprimidos ajenos» 5/5 ausente, con el argumento de que
+  # `libzip` es una BIBLIOTECA y no el comando. Cierto para `libzip` — y FALSO como conclusión:
+  # **`libarchive` estaba sellado y trae `bsdunzip`, `bsdtar`, `bsdcpio`, `bsdcat`**, y sólo se
+  # declaraba en `escritorio-kde`. O sea que las otras nueve imágenes no podían abrir un adjunto
+  # por un paquete que ya estaba construido.
+  #
+  # MEDIDO con un zip de verdad (deflate, con subdirectorio) antes de escribir esto:
+  #   bsdunzip -o x.zip          extrae y conserva el árbol           ✓
+  #   bsdtar -xf x.zip           ídem                                 ✓
+  #   bsdtar -a -cf x.zip ...    CREA un zip que python zipfile lee   ✓
+  #
+  # ⚠ Los binarios NO se llaman `unzip` ni `zip`, así que un script ajeno que invoque esos nombres
+  # sigue fallando y el censo sigue contándolos como huecos — hace bien: mide equivalencia por
+  # NOMBRE. Lo que cambia es que la CAPACIDAD está, y eso baja el hueco de «no hay camino» a
+  # «faltan los alias». Es la misma figura que `agetty` cubriendo a `getty` con otro nombre.
+  # ⚠ No se midió 7z ni rar: libarchive los soporta en lectura según upstream, pero sin un fichero
+  # de prueba no lo afirmo.
+  "libarchive",
 ]
 
 [perfil.escritorio-mirada]
diff --git a/scripts/censo-distro.py b/scripts/censo-distro.py
index 35be0906..5c460879 100755
--- a/scripts/censo-distro.py
+++ b/scripts/censo-distro.py
@@ -83,8 +83,11 @@ CHECKLIST = {
 
     # ── CAPACIDADES ENTERAS SIN NINGÚN CAMINO ───────────────────────────────────────────────────
     'archivos comprimidos ajenos': (
-        'no se puede abrir un .zip, un .7z ni un .rar — un adjunto cualquiera queda cerrado. '
-        '`libzip` es una BIBLIOTECA, no el comando',
+        '⚠ EL ARGUMENTO DE ESTA FILA ERA FALSO Y LO MEDÍ DESPUÉS: decía «no se puede abrir un .zip» '
+        'porque `libzip` es una biblioteca y no el comando. Cierto para libzip — y falso como '
+        'conclusión: `libarchive` estaba SELLADO y trae `bsdunzip`/`bsdtar`, verificados con un zip '
+        'real en lectura Y escritura. Se declaró en `cli` el 2026-10-19. Lo que falta son los ALIAS '
+        '(`unzip`, `zip`) para que un script ajeno que los invoque no falle, y 7z/rar sin medir',
         ['unzip', 'zip', 'p7zip', '7zip', 'unrar']),
     'imprimir de verdad': ('cups solo no imprime: los filtros y los drivers son otro paquete',
                            ['cups', 'cups-filters', 'ghostscript', 'gutenprint', 'hplip']),
@@ -156,6 +159,18 @@ CHECKLIST = {
 }
 
 
+# ── CAPACIDAD YA CUBIERTA CON OTRO NOMBRE ──────────────────────────────────────────────────────
+# Lo simétrico de `MUROS`: el renglón falta POR NOMBRE y la capacidad SÍ está, medida. No absuelve
+# el hueco —un script que invoque `unzip` sigue fallando, y el censo hace bien en contarlo— pero
+# cambia el presupuesto de «no hay camino» a «faltan los alias», que es otra cosa y mucho más
+# barata. Sólo entra lo verificado ejecutando, no lo que upstream promete.
+CUBIERTOS = {
+    'unzip': ('libarchive', 'bsdunzip -o x.zip extrae y conserva el árbol — probado con un zip '
+                            'deflate con subdirectorio el 2026-10-19'),
+    'zip':   ('libarchive', 'bsdtar -a -cf x.zip … crea un zip que python zipfile lee y verifica '
+                            '— probado el 2026-10-19'),
+}
+
 # ── POR QUÉ ESTÁ CADA HUECO: receta ausente ≠ capacidad ausente ─────────────────────────────────
 # «Falta la receta» y «la distro no publica eso» son dos cosas con presupuestos distintos, y el
 # censo mentía por omisión al contarlas juntas. Sólo entran acá los muros MEDIDOS, con el dato que
@@ -222,6 +237,8 @@ def main():
                 i['pista'] = 'nombre parecido en el catálogo (verificar, NO es equivalencia)'
             if i['nombre'] in MUROS:
                 i['muro'], i['muro_porque'] = MUROS[i['nombre']]
+            if i['nombre'] in CUBIERTOS:
+                i['cubierto_por'], i['cubierto_porque'] = CUBIERTOS[i['nombre']]
 
     print('== qué le falta a la distro para usarse (checklist de JUICIO, estado MEDIDO) ==')
     print('   recetas alcanzables: %d · de ellas en alguna imagen: %d'
@@ -237,6 +254,11 @@ def main():
         print('   — PISTAS: falta, pero hay un nombre parecido (verificar; NO es equivalencia):')
         for n, p in rev:
             print('        %-20s ~ %s' % (n, ', '.join(p)))
+    cub = [(i['nombre'], i['cubierto_por']) for f in filas for i in f['items'] if i.get('cubierto_por')]
+    if cub:
+        print('   — CAPACIDAD YA CUBIERTA con otro nombre (falta el alias, no el camino):')
+        for n, q in cub:
+            print('        %-20s lo hace %s' % (n, q))
     con_muro = [(i['nombre'], i['muro']) for f in filas for i in f['items'] if i.get('muro')]
     if con_muro:
         print('   — MUROS MEDIDOS (no es que falte la receta: la distro no publica eso):')