Files
takana/scripts/mudanza/formatos/proc.py
T
SergioandClaude Opus 5 d0e3aadb12 declarar: /proc pasa a ser un lector del centro — y había DOS emisores de tarjetas, mal los dos
`declarar.py` tenía su propio molde de card de arje y `formatos/arje.py` el suyo: el N×M que el
pivote existe para evitar, adentro de mi propio código. Y no quedó en teoría — CADA COPIA TENÍA UN
CAMPO MAL DE LA RAÍZ, Y NINGUNO DE LOS DOS EL MISMO:

    campo         declarar.py            arje.py        semilla REAL del producto
    provides      ["Spawn","Journal"] ✓  [] ✗           ["Spawn","Journal"]
    supervision   Restart{…} ✗           "OneShot" ✓    "OneShot"

Las consecuencias son concretas: sin `Spawn`/`Journal` las hijas no tienen quién las lance ni dónde
escribir, y una card `Virtual` con `Restart` le pide a arje que respawnee algo que nunca corrió.

Ahora hay un lector `proc` (el censo es un formato de origen, igual que systemd u OpenRC) y el
escritor `arje` es el ÚNICO que emite tarjetas; `declarar.py` queda con lo suyo, el perfil. Tres
lectores en la familia: systemd y openrc leen LO DECLARADO —que es lo que miente, `rc-status` daba
`stopped` para cinco servicios vivos— y `proc` lee LO QUE CORRE, que es donde aparecen los 15
`no-declarado`.

Dos cosas más, las dos sobre no mentir:

· **Una semilla vacía parecería un éxito.** Si ningún servicio tiene `decision = "muda"`, el lector
  lo DICE y sale ≠0 en vez de emitir cero tarjetas en silencio. Mismo modo de fallo que un artefacto
  vacío en el store.
· **El acta se ahogaba en su propio ruido.** Anotaba el `envp` vacío una vez POR SERVICIO: 26 líneas
  idénticas que tapaban los dos hallazgos reales (`shuma-daemon` corre como `sergio`; los que no
  tienen cmdline). La limitación es del lector y vale para todos ⇒ una entrada nombrando a los 26.
  Un acta donde casi todo es la misma línea se deja de leer, y entonces no queda ningún acta. Y al
  revés: las entradas que SÍ son por servicio ahora lo nombran (`gitea: cwd=/var/lib/gitea`).

Controles: la raíz generada coincide campo por campo con la del producto; dos corridas dan el fichero
byte a byte idéntico; 26 tarjetas con 26 ids únicos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-12 00:31:09 +00:00

78 lines
4.6 KiB
Python

"""Lector del CENSO (lo que `/proc` dice que corre) → modelo pivote «servicio» (SDD 29).
El tercer lector de la familia, y el que no tiene equivalente en ningún init: `systemd` y `openrc`
leen **lo declarado**, que es exactamente lo que miente — en gioser `rc-status` daba `stopped` para
cinco servicios que estaban vivos, y los `no-declarado` (15 de 39) no aparecen en ninguna
declaración porque, por definición, nadie los declaró. Éste lee **lo que corre**.
Los tres se complementan y ninguno solo alcanza. Que los tres desemboquen en el mismo escritor es
justamente el punto del pivote: la tarjeta de arje se emite en UN lugar, no uno por procedencia.
── LO QUE ESTE LECTOR NO PUEDE ─────────────────────────────────────────────────────────────────
El censo **no lee `/proc/<pid>/environ`** — trae tokens y contraseñas, y un censo se guarda en un
fichero que se manda por ahí. Así que el `envp` de toda tarjeta sale VACÍO. Un servicio que dependa
de variables de entorno va a arrancar y fallar, y eso se dice por su nombre, no en una nota general.
"""
import sys, os, tomllib
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from modelo import Servicio, Sistema # noqa: E402
from formatos import lector # noqa: E402
@lector("proc", familia="servicio")
def leer(texto, nombre=""):
sis = Sistema(formato_origen="proc")
acta = sis.acta
censo = tomllib.loads(texto)
servicios = censo.get("servicio", [])
if not servicios:
acta.anota(0, "SIN-TRADUCIR", nombre or "censo",
"el censo no tiene ningún `[[servicio]]`: ¿es el fichero correcto?")
return sis
decididos = [s for s in servicios if s.get("decision") == "muda"]
if not decididos:
# Salir con cero tarjetas y código 0 sería una semilla VACÍA que parece un éxito. Es el mismo
# modo de fallo que un artefacto vacío en el store: un ausente falla ruidosamente, un vacío
# llega hasta el final diciendo que todo fue bien.
acta.anota(0, "SIN-TRADUCIR", nombre or "censo",
f"ninguno de los {len(servicios)} servicios tiene `decision = \"muda\"`. La "
"semilla saldría VACÍA y parecería un éxito: decidí primero (`planear.py --decide`)")
return sis
for s in decididos:
srv = Servicio(nombre=s["name"], tipo="simple")
srv.cmd = (s.get("cmdline") or "").strip()
srv.cwd = (s.get("cwd") or "").strip()
srv.usuario = (s.get("usuario") or "").strip()
if srv.usuario:
srv.lineas["usuario"] = 0
# Está corriendo y se decidió mudarlo ⇒ se quiere arriba. Es una decisión, y se anota como
# tal: el censo no dice si estaba en el arranque, dice que está VIVO ahora.
srv.arranque_auto = True
srv.reinicio = "siempre"
if not srv.cmd:
acta.anota(0, "SIN-TRADUCIR", s["name"],
"no se pudo leer su línea de comando en el origen: NO se emite tarjeta. Una "
"tarjeta que no arranca es peor que una ausente — la ausente falla ruidosamente "
"al primer arranque, la rota deja el servicio caído sin que nada avise")
continue
if s.get("ports"):
acta.anota(0, "traducido", f"{s['name']} sirve {', '.join(str(p) for p in s['ports'])}")
sis.servicios.append(srv)
# ⚠ UNA entrada, no una por servicio. La limitación es del LECTOR y vale igual para todos, así
# que repetirla 26 veces no informa 26 veces: tapa los hallazgos que sí son de un servicio
# concreto (el que corre como `sergio`, el que no tiene cmdline). Un acta en la que casi todo es
# la misma línea se deja de leer, y entonces no queda ningún acta.
if sis.servicios:
acta.anota(0, "decision", f"supervisión de las {len(sis.servicios)} tarjetas",
"el censo dice que CORREN, no si el origen las levantaba al arrancar; se declaran "
"con reinicio automático porque se decidió mudarlas. Sacá del `genesis` las que no "
"quieras al boot")
acta.anota(0, "SIN-TRADUCIR", f"envp VACÍO en las {len(sis.servicios)} tarjetas",
"el censo NO lee `/proc/<pid>/environ` (trae tokens), así que ninguna tarjeta "
"lleva entorno. El que dependa de variables arranca y falla: " +
" ".join(x.nombre for x in sis.servicios))
return sis