Files
takana/scripts/mudanza/modelo.py
T
SergioandClaude Opus 5 3137d47f16 traducir: CENTRO de traducción con modelo pivote y plugins — N+M en vez de N×M
Traducir de a pares no escala: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y
cada formato nuevo agrega 2N. Con un modelo intermedio son N lectores + M escritores: un formato
nuevo cuesta uno o dos plugins y estrena todos los pares de golpe.

**Probado, no argumentado**: el lector de apache se agregó SIN TOCAR el escritor de Caddy, y con eso
`apache → caddy` funciona sin que exista ningún traductor de ese par. Las dos salidas las valida el
caddy del propio corpus: `Valid configuration` en las dos.

Los plugins se DESCUBREN (`formatos/` + `@lector` / `@escritor`): una lista mantenida a mano se
desincroniza y el formato nuevo «no existe» sin que nada falle. `--list` dice qué pares habilita.

**El acta viaja DENTRO del modelo**, y es la decisión que hace que el pivote no mienta: si fuera un
efecto de cada traductor, lo que el LECTOR no entendió se perdería antes de llegar al escritor. El
escritor además puede agregarle — Caddy no tiene equivalente de `location ~`, así que en vez de
emitir un `handle` de prefijo que SE PARECE, lo manda al acta. Parecerse es peor que faltar.

Y un bug del lector de apache, encontrado probando: `ProxyPass` tiene dos formas y dentro de
`<Location>` lleva sólo la URL. Mirar sólo la de tres tokens dejaba sin traducir la forma más común y
el acta la reportaba como «no reconocida» — un falso negativo que manda a escribir a mano algo que el
lector sí sabe hacer.

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

80 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""modelo.py — el MODELO PIVOTE de configuración de servicio, y el acta que lo acompaña (SDD 29).
── POR QUÉ UN PIVOTE Y NO PARES ────────────────────────────────────────────────────────────────
Traducir de a pares crece en N×M: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y
cada formato nuevo agrega 2N. Con un modelo intermedio son **N lectores + M escritores**: un formato
nuevo cuesta UNO o DOS plugins, y estrena todos los pares de golpe. Es la diferencia entre un centro
de traducción y una pila de conversores.
── EL ACTA VIAJA DENTRO DEL MODELO, NO AL COSTADO ──────────────────────────────────────────────
Ésta es la decisión que hace que el pivote no mienta. Si el acta fuera un efecto de cada traductor,
lo que un LECTOR no entendió se perdería antes de llegar al escritor. Viajando en el modelo, el
escritor puede emitirlo como comentario y el humano lo ve en el fichero que va a desplegar.
Doctrina heredada de `soltar`/`paskaq` (tawasuyu): **elisión honesta** — lo que no se pudo traducir
se reporta con su línea y su motivo, nunca se descarta en silencio.
"""
from dataclasses import dataclass, field
@dataclass
class Accion:
"""Qué hace una ruta. `tipo` ∈ {proxy, archivos, redir, responder, intentar}."""
tipo: str
valor: str = ""
extra: dict = field(default_factory=dict)
@dataclass
class Ruta:
"""Un camino dentro de un sitio. `clase` ∈ {prefijo, exacto, regex}.
`regex` se conserva a propósito aunque casi ningún destino lo soporte igual: **perder la
distinción sería traducir mal en silencio**. El escritor decide si sabe emitirla o la manda al
acta."""
patron: str
clase: str = "prefijo"
acciones: list = field(default_factory=list)
origen: int = 0
@dataclass
class Sitio:
nombres: list = field(default_factory=list)
puertos: list = field(default_factory=list) # "80", "443"
tls: dict = field(default_factory=dict) # {cert, key}
raiz: str = ""
indices: list = field(default_factory=list)
rutas: list = field(default_factory=list)
acciones: list = field(default_factory=list) # a nivel de sitio
origen: int = 0
@dataclass
class Acta:
"""Registro de procedencia. Cuatro estados, y el que importa es el primero."""
entradas: list = field(default_factory=list)
def anota(self, linea, estado, texto, motivo=""):
# estado ∈ {SIN-TRADUCIR, decision, traducido, no-aplica}
self.entradas.append({"linea": linea, "estado": estado,
"directiva": texto, "motivo": motivo})
def sin_traducir(self):
return [e for e in self.entradas if e["estado"] == "SIN-TRADUCIR"]
def por_estado(self):
d = {}
for e in self.entradas:
d.setdefault(e["estado"], []).append(e)
return d
@dataclass
class Config:
"""Lo que un lector produce y un escritor consume."""
formato_origen: str = ""
sitios: list = field(default_factory=list)
acta: Acta = field(default_factory=Acta)