# Señales

Una unidad habla con la plataforma a través de un pequeño agente que corre a su lado y comparte el directorio `/zaroz`. **No hay ningún puerto de red**: escribes archivos o llamas a un socket Unix.

## Lo que una unidad puede señalar (contrato v1, un conjunto cerrado)

| Señal | Significado |
|---|---|
| `ready` | He cargado; envíame tráfico. Solo se necesita con `readiness: signal` |
| `accepting` `true` o `false` | dentro o fuera del conjunto balanceado sin dejar de estar sana. `false` saca la unidad del tráfico y de la asignación en aproximadamente un segundo |
| `idle`, `busy` | lo que está haciendo la unidad. Guía `idle_action` y las decisiones de despliegue y de reducción. Solo se registran las transiciones |
| `shutdown` | He terminado. Igual que salir con código 0 |

Los nombres desconocidos los ignora y registra el agente, nunca son fatales.

## Con archivos

```bash
echo > /zaroz/signals/ready
echo false > /zaroz/signals/accepting     # llena: deja de enviar jugadores
echo true  > /zaroz/signals/accepting
echo > /zaroz/signals/idle
echo > /zaroz/signals/busy
echo > /zaroz/signals/shutdown
```

El agente consume cada archivo cuando aparece. Un archivo `accepting` vacío significa `true`.

## Con el socket

HTTP/1.1 sobre el socket Unix `/zaroz/signal.sock`:

```bash
curl --unix-socket /zaroz/signal.sock -X POST http://unit/ready
curl --unix-socket /zaroz/signal.sock -X POST http://unit/accepting -d false
curl --unix-socket /zaroz/signal.sock http://unit/unit
curl --unix-socket /zaroz/signal.sock http://unit/user-data
curl --unix-socket /zaroz/signal.sock "http://unit/allocation?wait=60"   # long-poll de hasta 60 s
curl --unix-socket /zaroz/signal.sock "http://unit/state?wait=60"        # responde cuando empieza el drenaje
```

## Lo que recibe una unidad

| Archivo | Contenido |
|---|---|
| `/zaroz/unit.json` | clave, etiquetas (se actualizan en vivo), origen, workload, nombre privado, endpoints públicos |
| `/zaroz/user-data.json` | tu `user_data`, o `null` |
| `/zaroz/allocation.json` | `null` hasta que se asigna; después `{id, allocated_at, generation}` |
| `/zaroz/state.json` | `{draining: false}` o `{draining: true, reason: "stop" \| "destroy" \| "replace" \| "scale_down"}` |

Cada archivo se escribe de forma atómica (un nombre temporal y después un renombrado), así que un lector nunca ve medio archivo. Vuelve a leerlos cuando cambien `allocation.json` o `state.json`; los long-polls `?wait=` del socket evitan sondear.

## Una unidad mínima

```ts
import { watch, readFileSync, writeFileSync } from "node:fs";

const dir = process.env.ZAROZ_DIR ?? "/zaroz";
const read = (f: string) => JSON.parse(readFileSync(`${dir}/${f}`, "utf8"));
const signal = (name: string, body = "") => writeFileSync(`${dir}/signals/${name}`, body);

await startMyServer();                 // carga el mapa, abre el puerto
signal("ready");

watch(`${dir}/allocation.json`, () => {
  const allocation = read("allocation.json");
  if (allocation) startMatch(read("user-data.json"));   // aquí llega la lista de jugadores
});

watch(`${dir}/state.json`, () => {
  if (read("state.json").draining) {
    signal("accepting", "false");       // deja de aceptar jugadores nuevos
    finishMatchThenExit();              // sal con 0 al terminar
  }
});
```

## A dónde van las señales

El agente las escribe en un objeto por unidad dentro del clúster, que la plataforma vigila. Nada de lo que se factura depende de una señal, así que un programa que mienta con sus señales solo puede detener, destruir por inactividad o marcar como llena **su propia** unidad.

Si la región no es alcanzable, las unidades siguen sirviendo y balanceando (la disponibilidad es local); la asignación responde `503 region-unreachable`.
