94e60c03407083c7e8d9dd74ffaa148fc619c642
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
053e5e7edf |
planear: buscar la receta por el PAQUETE del origen — y el intérprete no es el programa
`origen_binario` buscaba la receta por el nombre del SERVICIO, y ése casi nunca es el nombre del paquete: `sshd` lo trae `openssh`, `crond` lo trae `cronie`. El censo ya le preguntó al gestor de paquetes quién posee cada binario, así que ese nombre también se prueba. El perfil pasó de 4 recetas a 7, y los 27 servicios quedan: receta-takana 9 · suelto 9 · paquete-ajeno 13 · interprete 4 · borrado 4. **La trampa, que es la que haría mentir al perfil.** `openclaw` lo posee el paquete `nodejs`; `fail2ban-server`, `glances` y `uvicorn` los posee `python`. Contarlos como cubiertos porque existe `recipes/nodejs.toml` haría salir el perfil N/N describiendo un servidor al que le faltan CUATRO programas. Tienen clase propia (`interprete`) y cuentan las DOS cosas a la vez, porque las dos son ciertas: su runtime entra al perfil —`openclaw` necesita `nodejs` en la imagen pase lo que pase— y el servicio sigue listado como NO cubierto. Meterlo sólo en las raíces miente; dejarlo sólo en los faltantes arma una imagen sin runtime y el programa, cuando llegue, no arranca. Y el paquete del origen no se llama igual que la receta ni para el mismo intérprete: Artix empaqueta `python` y el catálogo tiene `python3.toml`. Sin ese alias tres servicios decían «hace falta una receta takana» teniendo el runtime sellado — dos trabajos muy distintos. **Sellada y sin sellar tampoco son lo mismo**, y el perfil las listaba igual: una sellada se INSTALA del repo firmado, una sin sellar hay que CONSTRUIRLA. Salió con un caso real — `recipes/qdrant.toml` entró al catálogo desde otro frente mientras se escribía esto y no tiene artefacto. Ahora se marca en la línea y se resume al pie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
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
|
||
|
|
fefa92ec5a |
traducir: familia «servicio» — systemd → tarjeta de arje, y el pivote pasa a ser uno POR FAMILIA
Un unit de systemd no es un sitio web. Meterlo en `Sitio`/`Ruta` sería justo el «parecerse» que el acta existe para evitar, así que cada familia tiene su modelo y el centro empareja SÓLO dentro de la familia. Un par cruzado se rechaza con un error, no con un intento: `systemd → caddy` sale por `sys.exit`, porque traducir entre familias daría un fichero que PARECE correcto. Dos familias hoy: `web` (nginx, apache → caddy) y `servicio` (systemd → arje). 3 pares con 5 plugins. **Dónde una elisión silenciosa no pierde una opción sino que CAMBIA el sistema.** El payload `Native` de arje acepta `exec`, `argv` y `envp` y nada más (comprobado en la semilla real del producto): NO hay campo de usuario. Un `User=git` traducido en silencio correría el servicio COMO ROOT — escalación de privilegios en un fichero generado que nadie vuelve a leer. Sale SIN-TRADUCIR con su línea. Igual `EnvironmentFile=` (el fichero no está en la máquina donde se traduce ⇒ envp incompleto, falla tarde), `Type=forking` (arje supervisa al proceso que lanza ⇒ BUCLE DE REINICIO) y todo el endurecimiento, que callado entrega un servicio MENOS confinado que el original. Lo que sí tiene destino exacto: `Type=oneshot` → `"supervision": "OneShot"`, que ya existe en la semilla del producto — buscarlo antes de declararlo intraducible evitó una elisión inventada. **Lector y escritor no opinan del mismo campo.** `User=` lo CAPTURA el lector y lo JUZGA el escritor; cuando los dos anotaban, el acta decía dos cosas distintas de la misma línea, y un acta que se contradice se deja de leer. El lector registra en qué línea vio cada campo (`Servicio.lineas`) para que el escritor señale la línea real en vez de un 0. Controles: la tarjeta generada tiene el MISMO juego de claves que la tarjeta real de `sshd` del producto (+`_mudanza` de procedencia); dos corridas dan el fichero byte a byte idéntico; y la familia `web` sigue validando con el caddy del corpus (`Valid configuration` en los dos pares). De paso, `ulid_determinista` sale a `ids.py`: lo necesitaban `declarar.py` y el escritor de arje, y copiarlo habría dejado dos generadores de id que se pueden separar sin que nada falle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
68fdd9eb72 |
mudanza: el perfil — el software del servidor nuevo se DECLARA, no se instala a mano
`declarar.py --perfil-out` emite un `[perfil.<label>]` listo para `targets.toml`, generado desde el censo: cada raíz está ahí porque un servicio que CORRÍA la necesita y takana tiene receta. Por qué un perfil y no una lista de `install`: mudar servicio por servicio a una caja viva va contra el diseño —el software de una máquina se declara y viene en la imagen, reproducible y firmado— y un servidor armado a fuerza de instalaciones sueltas no se puede volver a construir. Que es justo el problema que esta mudanza existe para no repetir: el `caddy` de gioser no tiene dueño ni receta y se pierde con la máquina. Y hay un impedimento medido, no estético: `takana install` REPRODUCE desde fuente y eso exige el lab entero en el cliente (SDD 28 §5.4), que una caja de destino no tiene. El perfil lo resuelve por el lado correcto: el software entra al armar la imagen, en el hub, que sí tiene lab. Del censo de gioser salen `caddy`, `gitea`, `python3` (agrupado: lo piden python3 y uvicorn). Verificado que el instrumental lo acepta: `targets.py gioser-mudado` expande a 33 raíces. ⚠ Y dice lo que NO puede cubrir, uno por uno con su motivo: 34 servicios entre `suelto`, `paquete-ajeno` y `desconocido`. Un perfil que se calla lo que le falta sale N/N describiendo un servidor incompleto — la lección de `foot` en escritorio-sway. Con esto la mudanza produce TRES documentos declarativos que reconstruyen el servidor desde cero: el perfil (qué software), la semilla (qué corre y cómo) y el plan (qué datos, en qué orden). Ninguno es un log de lo hecho: los tres son entradas que se vuelven a ejecutar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
1a89592b72 |
mudanza etapa 3: declarar.py — de «corre y nadie sabe cómo» a una Semilla que arranca
Copiar un binario no lo levanta al arrancar. Esto toma la invocación que el censo leyó de `/proc` y emite `seed.card.json`. Medido sobre gioser: **37 de 37 servicios vivos declarados, cero fallos**, y la tarjeta de caddy sale con su invocación real — exactamente lo que faltaba para que no muriera en el próximo reinicio. Por qué no alcanza con `arje-absorb`: absorb lee la DECLARACIÓN del init ajeno, que es justo la que miente (`rc-status` daba `stopped` para cinco servicios vivos), y los `no-declarado` no aparecen en ninguna declaración por definición. Absorber la declaración reproduce el agujero. Se complementan. Tres reglas: 1. Un servicio sin `cmdline` legible NO se emite y se dice: una tarjeta que no arranca es peor que una ausente — la ausente falla ruidosamente, la rota deja el servicio caído en silencio. 2. El `cwd` se preserva ENVOLVIENDO, porque el payload `Native` acepta `exec`/`argv`/`envp` y no `cwd` (comprobado sobre la semilla real). 9 de 37 servicios de gioser dependen de su directorio. 3. IDs deterministas ⇒ misma entrada, misma semilla BYTE A BYTE (verificado con sha256). Un fichero generado que cambia en cada corrida no se puede revisar con `diff`. **Y un bug del censo que costaba el 70 % del dato**: `cmdline` y `cwd` se leían en un solo comando, y como `readlink /proc/<pid>/cwd` exige permiso de ptrace, en cualquier proceso de root el comando entero salía ≠0 y se descartaba TAMBIÉN el `cmdline`. 26 de 37 servicios quedaban sin invocación. Sondas separadas. **Una sonda que falla es un dato; no puede arrastrar a las que funcionaron.** Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |