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 1534Shell 199Rust 109Markdown 993713 Definitions

⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65 scripts/mudanza/modelo.py Python · 121 lines · 5.5 KB History Who touched what raw

traducir: familia «servicio» — systemd → tarjeta de arje, y el pivote pasa a ser uno POR FAMILIA fefa92ec5 · Sergio ·

1#!/usr/bin/env python3
2"""modelo.py — el MODELO PIVOTE de configuración de servicio, y el acta que lo acompaña (SDD 29).
3
4── POR QUÉ UN PIVOTE Y NO PARES ────────────────────────────────────────────────────────────────
5Traducir de a pares crece en N×M: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y
6cada formato nuevo agrega 2N. Con un modelo intermedio son **N lectores + M escritores**: un formato
7nuevo cuesta UNO o DOS plugins, y estrena todos los pares de golpe. Es la diferencia entre un centro
8de traducción y una pila de conversores.
9
10── EL ACTA VIAJA DENTRO DEL MODELO, NO AL COSTADO ──────────────────────────────────────────────
11Ésta es la decisión que hace que el pivote no mienta. Si el acta fuera un efecto de cada traductor,
12lo que un LECTOR no entendió se perdería antes de llegar al escritor. Viajando en el modelo, el
13escritor puede emitirlo como comentario y el humano lo ve en el fichero que va a desplegar.
14
15Doctrina heredada de `soltar`/`paskaq` (tawasuyu): **elisión honesta** — lo que no se pudo traducir
16se reporta con su línea y su motivo, nunca se descarta en silencio.
17"""
18from dataclasses import dataclass, field
19
20
21@dataclass
22class Accion:
23    """Qué hace una ruta. `tipo` ∈ {proxy, archivos, redir, responder, intentar}."""
24    tipo: str
25    valor: str = ""
26    extra: dict = field(default_factory=dict)
27
28
29@dataclass
30class Ruta:
31    """Un camino dentro de un sitio. `clase` ∈ {prefijo, exacto, regex}.
32
33    `regex` se conserva a propósito aunque casi ningún destino lo soporte igual: **perder la
34    distinción sería traducir mal en silencio**. El escritor decide si sabe emitirla o la manda al
35    acta."""
36    patron: str
37    clase: str = "prefijo"
38    acciones: list = field(default_factory=list)
39    origen: int = 0
40
41
42@dataclass
43class Sitio:
44    nombres: list = field(default_factory=list)
45    puertos: list = field(default_factory=list)      # "80", "443"
46    tls: dict = field(default_factory=dict)          # {cert, key}
47    raiz: str = ""
48    indices: list = field(default_factory=list)
49    rutas: list = field(default_factory=list)
50    acciones: list = field(default_factory=list)     # a nivel de sitio
51    origen: int = 0
52
53
54@dataclass
55class Acta:
56    """Registro de procedencia. Cuatro estados, y el que importa es el primero."""
57    entradas: list = field(default_factory=list)
58
59    def anota(self, linea, estado, texto, motivo=""):
60        # estado ∈ {SIN-TRADUCIR, decision, traducido, no-aplica}
61        self.entradas.append({"linea": linea, "estado": estado,
62                              "directiva": texto, "motivo": motivo})
63
64    def sin_traducir(self):
65        return [e for e in self.entradas if e["estado"] == "SIN-TRADUCIR"]
66
67    def por_estado(self):
68        d = {}
69        for e in self.entradas:
70            d.setdefault(e["estado"], []).append(e)
71        return d
72
73
74@dataclass
75class Config:
76    """Lo que un lector produce y un escritor consume."""
77    formato_origen: str = ""
78    sitios: list = field(default_factory=list)
79    acta: Acta = field(default_factory=Acta)
80
81
82# ── FAMILIA «servicio» ──────────────────────────────────────────────────────────────────────────
83# ⚠ El pivote NO es uno solo: es uno POR FAMILIA. Un unit de systemd no es un sitio web, y meterlo a
84# la fuerza en `Sitio`/`Ruta` sería justo el «parecerse» que el acta existe para evitar. Cada familia
85# tiene su modelo, y el registro sólo empareja lectores con escritores de la MISMA familia — un par
86# cruzado se rechaza ruidosamente en vez de producir basura plausible.
87
88
89@dataclass
90class Servicio:
91    """Un proceso supervisado. El denominador común de systemd, OpenRC, runit, s6 y arje.
92
93    `usuario` se conserva aunque el destino no siempre sepa emitirlo: es el campo donde una elisión
94    silenciosa **escala privilegios** en vez de sólo perder una opción."""
95    nombre: str = ""
96    descripcion: str = ""
97    tipo: str = "simple"                             # simple | forking | oneshot | notify | dbus
98    cmd: str = ""                                    # la línea de arranque, tal cual
99    cwd: str = ""
100    usuario: str = ""
101    grupo: str = ""
102    entorno: dict = field(default_factory=dict)
103    entorno_ficheros: list = field(default_factory=list)   # no se pueden leer al traducir
104    tras: list = field(default_factory=list)         # orden (After=)
105    necesita: list = field(default_factory=list)     # dependencia dura (Requires=)
106    reinicio: str = "no"                             # no | siempre | si-falla
107    reinicio_ms: int = 0
108    arranque_auto: bool = False                      # ¿lo levanta el arranque? (WantedBy=)
109    plantilla: bool = False                          # `foo@.service`: no instanciable a ciegas
110    origen: int = 0
111    # {campo: línea} — el lector anota DÓNDE vio cada cosa para que el escritor pueda señalar la
112    # línea real en vez de un 0. Un acta que dice «línea 0» obliga a buscar a mano lo que ya se sabía.
113    lineas: dict = field(default_factory=dict)
114
115
116@dataclass
117class Sistema:
118    """Lo que produce un lector de la familia «servicio» y consume su escritor."""
119    formato_origen: str = ""
120    servicios: list = field(default_factory=list)
121    acta: Acta = field(default_factory=Acta)