los nueve daemons propios declarados — y la línea entre «se muda» y «se INTERCAMBIA»
Nueve de las diez sellaron y están promovidas. Cinco corren en la caja (matilda, tupu, pacha, pacha-secretos, willay-daemon, más el par de shuma de ayer); cuatro quedan instaladas y FRENADAS a propósito, cada una con su guarda diciendo qué falta. ⚠ LA PAUTA DE LOS VHOSTS NO SIRVE PARA UNA IDENTIDAD. Con los sitios la regla fue «mudá el DNS y dejá el origen encendido, así volver cuesta un minuto» (§6.26). Con `tejido` es justo lo contrario: su README dice que la clave que el roster atesta ES la identidad de transporte libp2p (`~/.tejido/device.seed`), así que dos máquinas con la misma semilla no son dos réplicas — son el MISMO PeerId en dos sitios, dos impostores mutuos para la flota. `tejido` se INTERCAMBIA: se apaga allá, se enciende acá, en ese orden. Todo listo para el intercambio (binario, cuenta 964, identidad 700 instalada) y su Card en `cards.d` pero NO en el `genesis`, para que un reinicio no lo encienda. Y arrastra a `willay-crosscheck`, que no es independiente: corriendo su línea a mano dice «no hay roster en /root/.tejido/roster.postcard — emparejá primero». Vive dentro de la red de tejido. `thasnuna` tampoco puede: su INSTALAR.md —escrito hoy por el frente tawasuyu para esta mudanza— pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es glibc de ~300 M (jaula qorpa). De ahí sale un hallazgo que vale para el respaldo entero: la Card `openrc-openclaw` de gioser lleva la API key de su proveedor EN CLARO dentro del JSON de /etc/arje/cards.d/, que se respalda y se copia. Un secreto dentro de una Card viaja a todas partes. `--label` de willay-crosscheck NOMBRA A LA MÁQUINA: en gioser decía `momento`. Ahora sale de `hostname` y la guarda frena si no hay. Y tres veredictos FALSOS en una tarde, los tres por probar con lo que la caja no tiene: `/dev/tcp` (busybox ash no lo tiene) dio los 5 puertos de gioser «cerrados»; la expansión de llaves dijo que los artefactos no habían llegado; y `find -newermt "-2 minutes"` dijo que tupu no escribía. Tupu SÍ escribe: su fichero tiene tamaño CONSTANTE —es una serie fija— así que ni los bytes ni ese find prueban nada; lo que decide es el mtime, medido dos veces con 40 s de por medio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
12 archivos · +332 −163
- docs/28-servidor-de-produccion.md +60 −0
- docs/state/targets.toml +18 −1
- recipes/incoming/tejido.toml +0 −28
- recipes/incoming/thasnuna.toml +0 −26
- recipes/incoming/tupu.toml +0 −54
- recipes/incoming/willay-crosscheck.toml +0 −28
- recipes/incoming/willay-daemon.toml +0 −26
- recipes/tejido.toml +55 −0
- recipes/thasnuna.toml +52 −0
- recipes/tupu.toml +54 −0
- recipes/willay-crosscheck.toml +52 −0
- recipes/willay-daemon.toml +41 −0
diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md
index 69afd7aa..6be4ca08 100644
--- a/docs/28-servidor-de-produccion.md
+++ b/docs/28-servidor-de-produccion.md
@@ -2293,6 +2293,66 @@ Dos veredictos falsos en una tarde, los dos por probar con algo que la caja no t
Es la misma lección del §6.23 (`ps` y `dig` ausentes) y merece repetirse: *una prueba que no puede
pasar se ve igual que una que todavía no pasó*.
+### 6.39 🕸️ Los nueve daemons propios, y la línea que separa «se muda» de «se INTERCAMBIA» *(2026-09-16)*
+
+Nueve de las diez recetas sellaron. Cinco corren; cuatro están instaladas y **frenadas a propósito**,
+cada una por un motivo distinto y con su guarda diciéndolo.
+
+| corriendo | | frenado | por qué |
+|---|---|---|---|
+| `matilda` · `tupu` | root, escriben `/var/lib/tupu/takana/` | `sandokan-watch` | en takana no hay `auth.log` ni journalctl |
+| `pacha` · `pacha-secretos` | cuenta `pacha` (965) | `tejido` | **misma identidad libp2p que el de gioser** |
+| `willay-daemon` | root, con su índice mudado | `willay-crosscheck` | necesita el roster de tejido |
+| `shuma-daemon` · `shuma-gateway` | cuenta `shuma` (§6.37) | `thasnuna` | pide `claude` autenticado y `sandokan-mcp` |
+
+#### ⚠ La regla que sirvió para los vhosts NO sirve para una identidad
+
+Con los sitios web la pauta fue: **mudá el DNS y dejá el origen encendido**, así volver cuesta un
+minuto (§6.26). Con `tejido` eso es exactamente lo que NO hay que hacer, y el README del propio
+tejido lo dice: *«la clave que el roster atesta ES la identidad de transporte libp2p»*
+(`~/.tejido/device.seed`). Dos máquinas con la misma semilla no son dos réplicas del mismo servicio:
+son **el mismo PeerId en dos sitios**, o sea dos impostores mutuos para el resto de la flota.
+
+⇒ `tejido` se **intercambia**: se apaga allá, se enciende acá, en ese orden. Está todo listo para el
+intercambio —binario, cuenta `tejido` (964), identidad instalada 700 en `/var/lib/tejido/.tejido/`—
+y su Card vive en `cards.d` pero **no en el `genesis`**, para que un reinicio no lo encienda solo.
+
+Y arrastra a otro: `willay-crosscheck` **no es independiente**. Corriendo su línea a mano:
+
+```
+Error: no hay roster en /root/.tejido/roster.postcard — emparejá primero (`tejido serve` / `tejido join`)
+```
+
+Vive dentro de la red de tejido, así que hereda su condición. Se enciende en el mismo movimiento.
+
+#### Lo que cada guarda evita
+
+- `thasnuna`: su `INSTALAR.md` —escrito hoy por el frente tawasuyu para esta misma mudanza— pide
+ tres cosas, y dos no están: `sandokan-mcp` en el PATH («el agente contesta pero no tiene
+ herramientas») y **`claude` instalado y AUTENTICADO** («no hay anfitrión»). El CLI de claude es un
+ binario **glibc de ~300 M**: en una caja musl pide jaula qorpa, como `sergioh-api`.
+- Y de ahí sale un hallazgo que vale para el respaldo entero: ese documento explica por qué el token
+ no va en la Card, y el motivo está medido en la máquina vieja — **la Card `openrc-openclaw` de
+ gioser lleva la API key de su proveedor EN CLARO dentro del JSON de `/etc/arje/cards.d/`**, que es
+ un directorio que se respalda y se copia. Un secreto dentro de una Card viaja a todas partes.
+- `willay-crosscheck`: `--label` **nombra a la máquina**. En gioser decía `momento`; copiarlo tal
+ cual habría hecho que la caja nueva se anunciara con el nombre de la que se borra. Ahora sale de
+ `hostname`, y si no hay, la guarda lo dice.
+
+#### 🔁 Tres veredictos falsos en una tarde, los tres por probar con lo que la caja no tiene
+
+Van con el §6.38 y ya son patrón, no anécdota:
+
+| la prueba | lo que dijo | lo que pasaba |
+|---|---|---|
+| `/dev/tcp/host/puerto` | los 5 puertos de gioser «cerrados» | busybox `ash` no tiene `/dev/tcp` |
+| `ls /store/*-{a,b}` | «los artefactos no llegaron» | busybox no expande llaves |
+| `find … -newermt "-2 minutes"` | «tupu no escribe hace 2 min» | tupu **sí** escribe: su `mtime` avanza cada 10 s |
+
+El de `tupu` es el más instructivo porque casi produce un diagnóstico al revés: el fichero tiene
+**tamaño constante** (es una serie de tamaño fijo), así que «no cambió de bytes» tampoco probaba
+nada. Lo que decide es el `mtime`, medido dos veces con 40 s de por medio.
+
## 7. Reusar los scripts que ya existen, y no escribir de nuevo
Pedido explícito del usuario. El inventario de lo que ya hace el trabajo:
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 25f89892..185e0e37 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -1338,6 +1338,12 @@ paquetes = [
# `sergio.gioser.net`. Construidos desde el monorepo de tawasuyu por decisión del usuario, en vez
# de mudar el binario glibc de gioser —cuyo inodo, además, ya estaba BORRADO del disco.
"shuma-daemon", "shuma-gateway",
+ # ── los daemons propios del usuario (2026-09-16) ──────────────────────────────────────────────
+ # Decisión suya: «viven todos los tawasuyu». Se construyen desde el monorepo, no se copian — tres
+ # de ellos corrían en gioser desde un INODO BORRADO. Entran al perfil como PAQUETES todos; cuáles
+ # ARRANCAN solos es otra pregunta y está más abajo, en `servicios`.
+ "matilda", "tupu", "sandokan-watch", "pacha", "pacha-secretos",
+ "willay-daemon", "willay-crosscheck", "tejido", "thasnuna",
# `curl`/`wget`: bajar del mirror y del repo. `takana install --repo https://…` los necesita del
# lado del cliente, y son también la única forma de diagnosticar un origen caído desde la caja.
"curl", "wget",
@@ -1447,7 +1453,18 @@ paquetes = [
# La cuenta `shuma` la declara `recipes/shuma-daemon.toml` con `[[user]]`, sin privilegio y con
# `shell = /bin/sh` —no `/bin/false`—, porque este demonio existe para abrir PTYs: con la shell
# inerte el servicio arranca, se supervisa, y cada pestaña muere al instante.
-servicios = ["sshd", "gitea", "caddy", "minga", "squid", "crond", "chronyd", "shuma-daemon", "shuma-gateway"]
+# ⚠ CUATRO DE LOS NUEVE DAEMONS PROPIOS **NO** ESTÁN ACÁ, Y ES LO IMPORTANTE DE ESTA LÍNEA:
+# · `sandokan-watch` — en takana no hay `auth.log` ni journalctl: arrancaría verde SIN VIGILAR.
+# · `tejido` y `willay-crosscheck` — comparten la identidad libp2p (`device.seed`) con el tejido
+# que TODAVÍA CORRE en gioser. Dos máquinas con la misma semilla son el MISMO PeerId: no son dos
+# réplicas, son dos impostores mutuos. Se encienden en el mismo movimiento en que el viejo se
+# apaga — es un INTERCAMBIO, no una convivencia, y por eso no van al arranque automático.
+# · `thasnuna` — su INSTALAR.md pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es
+# glibc: pide jaula qorpa.
+# Sus Cards están instaladas en `cards.d` (se pueden encarnar a pedido) y sus guardas salen 78
+# diciendo qué falta. La distinción `cards.d` vs `genesis` es exactamente ésta.
+servicios = ["sshd", "gitea", "caddy", "minga", "squid", "crond", "chronyd", "shuma-daemon", "shuma-gateway",
+ "matilda", "tupu", "pacha", "pacha-secretos", "willay-daemon"]
# ════════════════════════════════════════════════════════════════════════════════════════════════
[perfil.metal-tigerlake]
diff --git a/recipes/incoming/tejido.toml b/recipes/incoming/tejido.toml
deleted file mode 100644
index 56ecdd28..00000000
--- a/recipes/incoming/tejido.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-# tejido — el relay de la flota: `tejido relay` en gioser, sirviendo :4102 y :33097.
-#
-# ⚠ Su IDENTIDAD no es reproducible: `~/.tejido/{persona,device,relay}.seed` + el padrón
-# (`roster.postcard`). Generar semillas nuevas no es «reinstalar»: es ser OTRA máquina para el
-# resto de la flota y perder los teléfonos dados de alta. El binario se construye; la identidad
-# se MUDA. La Card tiene que exigirla, no crearla.
-#
-# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
-# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
-# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
-# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
-
-name = "tejido"
-version = "0.1.0"
-license = "MIT OR Apache-2.0"
-
-[source]
-repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
-commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
-# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
-# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
-cargo_vendor_dir = ".hammer-cargo-vendor"
-
-[build]
-compiler = "zig-cc"
-target = "x86_64-linux-musl"
-link = "static"
-flags = ["-p", "tejido", "--bin", "tejido"]
diff --git a/recipes/incoming/thasnuna.toml b/recipes/incoming/thasnuna.toml
deleted file mode 100644
index 1b77e740..00000000
--- a/recipes/incoming/thasnuna.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-# thasnuna — el bot: `thasnuna run --wait 25`.
-#
-# ⚠ Su config NO está en el repo a propósito (lo dice su INSTALAR.md): el token de telegram y el
-# roster de teléfonos viven en `~/.config/thasnuna/`. Sin eso el binario está y el bot no existe.
-#
-# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
-# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
-# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
-# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
-
-name = "thasnuna"
-version = "0.1.0"
-license = "MIT OR Apache-2.0"
-
-[source]
-repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
-commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
-# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
-# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
-cargo_vendor_dir = ".hammer-cargo-vendor"
-
-[build]
-compiler = "zig-cc"
-target = "x86_64-linux-musl"
-link = "static"
-flags = ["-p", "thasnuna-daemon", "--bin", "thasnuna"]
diff --git a/recipes/incoming/willay-crosscheck.toml b/recipes/incoming/willay-crosscheck.toml
deleted file mode 100644
index 3b7674f4..00000000
--- a/recipes/incoming/willay-crosscheck.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-# willay-crosscheck — el cruce de willay, que ESCUCHA en la red.
-#
-# ⚠ El paquete NO se llama como el binario: el crate es `willay-cruce` y el ejecutable
-# `willay-crosscheck`. Por eso van `-p` y `--bin`, que no son redundantes acá.
-# Medido en gioser: `--listen /ip4/0.0.0.0/tcp/4103 --label momento`. La etiqueta nombra a la
-# MÁQUINA, así que en la caja nueva no puede decir `momento` — se decide al escribir la Card.
-#
-# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
-# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
-# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
-# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
-
-name = "willay-crosscheck"
-version = "0.1.0"
-license = "MIT OR Apache-2.0"
-
-[source]
-repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
-commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
-# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
-# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
-cargo_vendor_dir = ".hammer-cargo-vendor"
-
-[build]
-compiler = "zig-cc"
-target = "x86_64-linux-musl"
-link = "static"
-flags = ["-p", "willay-cruce", "--bin", "willay-crosscheck"]
diff --git a/recipes/tejido.toml b/recipes/tejido.toml
new file mode 100644
index 00000000..50d60c2a
--- /dev/null
+++ b/recipes/tejido.toml
@@ -0,0 +1,55 @@
+# tejido — el relay de la flota: `tejido relay` en gioser, sirviendo :4102 y :33097.
+#
+# ⚠ Su IDENTIDAD no es reproducible: `~/.tejido/{persona,device,relay}.seed` + el padrón
+# (`roster.postcard`). Generar semillas nuevas no es «reinstalar»: es ser OTRA máquina para el
+# resto de la flota y perder los teléfonos dados de alta. El binario se construye; la identidad
+# se MUDA. La Card tiene que exigirla, no crearla.
+#
+# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
+# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
+# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
+# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
+
+name = "tejido"
+version = "0.1.0"
+license = "MIT OR Apache-2.0"
+
+[source]
+repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
+commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
+# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
+# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
+cargo_vendor_dir = ".hammer-cargo-vendor"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = ["-p", "tejido", "--bin", "tejido"]
+
+# ── LA CUENTA Y EL SERVICIO (SDD 30) ────────────────────────────────────────────────────────────
+# Fuera de `hash_inputs`. En gioser corre como `sergio`, nunca como root ⇒ cuenta propia.
+[[user]]
+name = "tejido"
+uid = 964
+home = "/var/lib/tejido"
+
+# ⚠⚠ **ESTE NO SE PUEDE ENCENDER MIENTRAS EL VIEJO SIGA VIVO, y es distinto de todo lo anterior.**
+# Con los vhosts la regla fue «mudá el DNS y dejá el origen encendido, así volver cuesta un minuto»
+# (§6.26). Acá NO vale: el README de tejido dice que **la clave que el roster atesta ES la identidad
+# de transporte libp2p** (`~/.tejido/device.seed`). Dos máquinas con la misma semilla son el MISMO
+# PeerId en la red — no dos réplicas: dos impostores mutuos. Por eso la Card queda en `cards.d` y
+# **no entra al `genesis`**: se enciende cuando el de gioser se apaga, en ese orden, y es un
+# INTERCAMBIO, no una convivencia.
+#
+# Y por eso la guarda EXIGE la identidad en vez de crearla: un `tejido` que genera semillas nuevas
+# arranca perfecto y es OTRA máquina para la flota — se pierde el padrón de teléfonos dados de alta.
+[[service]]
+label = "tejido"
+id = "01M2EKDA00TEJ1D0RELAY00001"
+exec = "/bin/busybox"
+argv = ["sh", "-c", "/bin/grep -q '^tejido:' /etc/passwd || { echo 'tejido: falta el usuario `tejido` en /etc/passwd — lo declara esta receta con [[user]]' >&2; exit 78; }; test -s /var/lib/tejido/.tejido/device.seed || { echo 'tejido: falta /var/lib/tejido/.tejido/device.seed — es la IDENTIDAD libp2p de esta máquina y NO se genera: viene de ~/.tejido del origen. Generar otra es ser otra máquina para la flota' >&2; exit 78; }; cd /var/lib/tejido || exit 78; exec /usr/bin/setuidgid tejido /usr/bin/tejido relay"]
+envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/var/lib/tejido"], ["USER", "tejido"]]
+networking = "full"
+cgroup = "arje.slice/tejido"
+restart = { initial_ms = 1000, max_ms = 30000 }
diff --git a/recipes/thasnuna.toml b/recipes/thasnuna.toml
new file mode 100644
index 00000000..82aa3577
--- /dev/null
+++ b/recipes/thasnuna.toml
@@ -0,0 +1,52 @@
+# thasnuna — el bot: `thasnuna run --wait 25`.
+#
+# ⚠ Su config NO está en el repo a propósito (lo dice su INSTALAR.md): el token de telegram y el
+# roster de teléfonos viven en `~/.config/thasnuna/`. Sin eso el binario está y el bot no existe.
+#
+# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
+# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
+# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
+# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
+
+name = "thasnuna"
+version = "0.1.0"
+license = "MIT OR Apache-2.0"
+
+[source]
+repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
+commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
+# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
+# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
+cargo_vendor_dir = ".hammer-cargo-vendor"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = ["-p", "thasnuna-daemon", "--bin", "thasnuna"]
+
+# ── LA CUENTA Y EL SERVICIO (SDD 30) ────────────────────────────────────────────────────────────
+[[user]]
+name = "thasnuna"
+uid = 963
+home = "/var/lib/thasnuna"
+
+# ⚠ **HOY NO PUEDE CORRER EN LA CAJA, y conviene que lo diga él y no que lo descubra alguien.**
+# Su propio `INSTALAR.md` —escrito el 2026-09-16 para esta misma mudanza— lista tres requisitos, y
+# dos no están: `sandokan-mcp` en el PATH (sin él «el agente contesta pero no tiene herramientas») y
+# **`claude` instalado Y AUTENTICADO** (sin él «no hay anfitrión»). El CLI de claude es un binario
+# glibc de ~300 M: en una caja musl pide jaula qorpa, igual que `sergioh-api`.
+#
+# Las tres guardas salen 78 nombrando lo que falta. El token NO se genera: lo da @BotFather y vive
+# en un fichero 600 del usuario. Su INSTALAR.md explica por qué no va en la Card, y el motivo está
+# medido en la máquina vieja: la Card `openrc-openclaw` de gioser lleva **la API key de su proveedor
+# EN CLARO dentro del JSON de `/etc/arje/cards.d/`**, que es un directorio que se respalda y se copia.
+[[service]]
+label = "thasnuna"
+id = "01M2EKDA00THA5NVNAB0T00001"
+exec = "/bin/busybox"
+argv = ["sh", "-c", "/bin/grep -q '^thasnuna:' /etc/passwd || { echo 'thasnuna: falta el usuario `thasnuna` en /etc/passwd' >&2; exit 78; }; test -s /var/lib/thasnuna/.config/thasnuna/telegram-token || { echo 'thasnuna: falta el token (~/.config/thasnuna/telegram-token, 600). Lo da @BotFather y NO va en esta Card: un JSON de cards.d se respalda y se copia' >&2; exit 78; }; command -v sandokan-mcp >/dev/null || { echo 'thasnuna: falta `sandokan-mcp` en el PATH — sin él el agente contesta pero NO TIENE HERRAMIENTAS (su INSTALAR.md §0)' >&2; exit 78; }; command -v claude >/dev/null || { echo 'thasnuna: falta `claude` instalado y AUTENTICADO — sin anfitrión el daemon cae al eco. Es glibc: en musl pide jaula qorpa' >&2; exit 78; }; cd /var/lib/thasnuna || exit 78; exec /usr/bin/setuidgid thasnuna /usr/bin/thasnuna run --wait 25"]
+envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/var/lib/thasnuna"], ["USER", "thasnuna"]]
+networking = "full"
+cgroup = "arje.slice/thasnuna"
+restart = { initial_ms = 1000, max_ms = 30000 }
diff --git a/recipes/incoming/tupu.toml b/recipes/tupu.toml
similarity index 100%
rename from recipes/incoming/tupu.toml
rename to recipes/tupu.toml
diff --git a/recipes/willay-crosscheck.toml b/recipes/willay-crosscheck.toml
new file mode 100644
index 00000000..ebea2971
--- /dev/null
+++ b/recipes/willay-crosscheck.toml
@@ -0,0 +1,52 @@
+# willay-crosscheck — el cruce de willay, que ESCUCHA en la red.
+#
+# ⚠ El paquete NO se llama como el binario: el crate es `willay-cruce` y el ejecutable
+# `willay-crosscheck`. Por eso van `-p` y `--bin`, que no son redundantes acá.
+# Medido en gioser: `--listen /ip4/0.0.0.0/tcp/4103 --label momento`. La etiqueta nombra a la
+# MÁQUINA, así que en la caja nueva no puede decir `momento` — se decide al escribir la Card.
+#
+# Receta Cargo contra el monorepo de tawasuyu, pinneada al MISMO commit que `shuma-*` y
+# `puriy-costura`: el árbol de fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro
+# vendoreo de 2,4 G. Estática musl con zig-cc — la caja la arranca el init y no puede depender de
+# encontrar un cargador, que es justo lo que le falta al binario glibc de gioser.
+
+name = "willay-crosscheck"
+version = "0.1.0"
+license = "MIT OR Apache-2.0"
+
+[source]
+repo = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
+commit = "23a292863f53b355c5c02d6185904f2c33d8ab42"
+# tawasuyu COMMITEA su propio `vendor/` (smithay parcheado, enchufado por `[patch.crates-io]` POR
+# RUTA); el `cargo vendor` de takana lo pisaría y el error habla de `taffy`, no de esto.
+cargo_vendor_dir = ".hammer-cargo-vendor"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = ["-p", "willay-cruce", "--bin", "willay-crosscheck"]
+
+# ── EL SERVICIO (SDD 30) ────────────────────────────────────────────────────────────────────────
+# ⚠ `--label` NOMBRA A LA MÁQUINA. En gioser decía `momento`, y copiarlo tal cual habría puesto a la
+# caja nueva a identificarse con el nombre de la que se borra. Sale de `hostname`, que desde hoy la
+# caja tiene de verdad (§6.38: antes decía `(none)`) — y si algún día vuelve a no tenerlo, la guarda
+# lo dice en vez de anunciarse como «(none)» en la red.
+[[service]]
+label = "willay-crosscheck"
+id = "01M2EKDA00W1YCR055CHK00012"
+exec = "/bin/busybox"
+argv = ["sh", "-c", "h=$(hostname); case \"$h\" in \"\"|\"(none)\") echo 'willay-crosscheck: la máquina no tiene hostname y `--label` es como se anuncia en la red — arreglá /etc/hostname (hay una Card OneShot que lo aplica)' >&2; exit 78;; esac; test -s /root/.tejido/roster.postcard || { echo 'willay-crosscheck: no hay roster en /root/.tejido/roster.postcard — este servicio vive DENTRO de la red de tejido y sin padrón no tiene con quién cruzar. Llega con el intercambio de tejido, no antes' >&2; exit 78; }; exec /usr/bin/willay-crosscheck --listen /ip4/0.0.0.0/tcp/4103 --label \"$h\""]
+envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/root"]]
+networking = "full"
+cgroup = "arje.slice/willay-crosscheck"
+restart = { initial_ms = 1000, max_ms = 30000 }
+
+# ⚠ MEDIDO AL ARRANCARLO (2026-09-16): **no es independiente de `tejido`**. Corriendo la misma línea
+# a mano en la caja:
+#
+# Error: no hay roster en /root/.tejido/roster.postcard — emparejá primero (`tejido serve` / `tejido join`)
+#
+# O sea que `willay-crosscheck` vive DENTRO de la red de tejido y hereda su condición: **se enciende
+# en el mismo movimiento en que tejido se intercambia**, no antes y no en paralelo con el de gioser.
+# Por eso sale del `genesis` y queda sólo en `cards.d`, como `tejido`.
diff --git a/recipes/incoming/willay-daemon.toml b/recipes/willay-daemon.toml
similarity index 51%
rename from recipes/incoming/willay-daemon.toml
rename to recipes/willay-daemon.toml
index 1bfced3d..195d4da8 100644
--- a/recipes/incoming/willay-daemon.toml
+++ b/recipes/willay-daemon.toml
@@ -24,3 +24,18 @@ compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "static"
flags = ["-p", "willay-daemon", "--bin", "willay-daemon"]
+
+# ── EL SERVICIO (SDD 30) ────────────────────────────────────────────────────────────────────────
+# Fuera de `hash_inputs`. En gioser corre como ROOT y escribe `/var/log/willay-daemon.log`; su
+# estado son 5,3 M en `/var/lib/willay`, que la mudanza ya copió. Se declara como está —root— en vez
+# de bajarlo a ciegas: lo que hace un demonio de vigilancia con menos privilegio del que tenía es
+# fallar en silencio, y eso no se ve en `arjectl status`.
+[[service]]
+label = "willay-daemon"
+id = "01M2EKDA00W1YDAEM0N0000001"
+exec = "/bin/busybox"
+argv = ["sh", "-c", "test -d /var/lib/willay || { echo 'willay-daemon: falta /var/lib/willay — su estado (el indice) viene con la mudanza, no se regenera' >&2; exit 78; }; exec /usr/bin/willay-daemon"]
+envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/root"]]
+networking = "full"
+cgroup = "arje.slice/willay-daemon"
+restart = { initial_ms = 1000, max_ms = 30000 }