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…
⎇ 3137d47f165b900042653af6db5513180fe471c8
scripts/mudanza/modelo.py
Ñawpa willay
Pi imata llamkarqan
ch'uya
traducir: CENTRO de traducción con modelo pivote y plugins — N+M en vez de N×M
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)