feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + keypair persistente)

Cierra el ultimo pendiente del plan de red: Arje ahora puede arrancar
opcionalmente con BrahmanNet configurado, persistir su identidad
libp2p entre reboots, y participar en la malla brahman como nodo
publico. Sin breaking changes: usuarios actuales (sin env vars) siguen
viendo el comportamiento Unix-only de antes.

Activacion por env vars:
- BRAHMAN_LISTEN_MULTIADDR — si set, activa la red P2P. Ej:
  /ip4/0.0.0.0/tcp/4101 (publico), /ip4/127.0.0.1/tcp/0 (loopback).
- BRAHMAN_KEYPAIR_PATH — override del path donde se persiste la
  keypair Ed25519. Defaults: PID 1 -> /var/lib/brahman/init-keypair.bin;
  dev mode -> $XDG_DATA_HOME/brahman/init-keypair.bin con fallbacks.
- BRAHMAN_BOOTSTRAP_PEERS — multiaddrs coma-separados a dial-ear al
  arranque para entrar al DHT.

Comportamiento al activarse:
1. keypair_store::load_or_generate carga keypair de disco o genera y
   persiste una nueva (32 bytes raw, 0o600, atomic rename). peer_id
   estable across reboots.
2. BrahmanNet::with_keypair arma el swarm con esa identidad.
3. net.listen() resuelve la addr y se loggea.
4. BRAHMAN_BOOTSTRAP_PEERS si set -> dial cada multiaddr.
5. ServerConfig.net = Some(net), activando announce_outputs automatico
   en el DHT por cada Card con outputs.
6. Ademas del Unix accept loop, se monta libp2p accept loop sobre el
   mismo Server compartido (Arc<Server>). Sesiones locales y remotas
   conviven en las mismas tablas.

Refactor del Unix accept loop: antes consumia server via run().await;
ahora usa Arc<Server>::accept_one().await en loop para coexistir con
el libp2p accept loop sin moverse el server.

Degradacion gracil: si la keypair no carga, multiaddr invalido,
listen falla, bootstrap dial revienta -> log + continuamos en
Unix-only. Doctrina PID 1 ("ningun subsistema opcional rompe el
bucle primordial") preservada.

Tests: 4 unit en keypair_store: generate_persist_and_reload_yields_
same_peer_id (la property fundamental), rejects_corrupted_file,
persisted_file_is_owner_only (0o600 verificados), default_path_
honors_env.

Activacion en una linea:
  BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero

Pendientes: stop_providing en cleanup, allowlist/denylist (PKI),
rotacion de keypair sin perder peer_id.
This commit is contained in:
Sergio
2026-05-09 15:02:00 +00:00
parent c164e9f422
commit fa0ed98880
5 changed files with 396 additions and 7 deletions
+74
View File
@@ -6,6 +6,80 @@ ratio/diff ver `git show <sha>`.
## 2026-05-09
### feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + identidad persistente)
Cierra el último pendiente del plan de red: Arje ahora puede arrancar
opcionalmente con `BrahmanNet` configurado, persistir su identidad
libp2p entre reboots, y participar en la malla brahman como nodo
público. Sin breaking changes: usuarios actuales (sin env vars) siguen
viendo el comportamiento Unix-only de antes.
Activación por env vars:
- **`BRAHMAN_LISTEN_MULTIADDR`** — si set, activa la red P2P. Ej:
`/ip4/0.0.0.0/tcp/4101` (público), `/ip4/127.0.0.1/tcp/0` (loopback,
port aleatorio). Sin la var, `brahman_net = None` y todo sigue
como antes.
- **`BRAHMAN_KEYPAIR_PATH`** — override del path donde se persiste
la keypair Ed25519 de identidad libp2p del nodo. Defaults sensatos:
- PID 1 (root): `/var/lib/brahman/init-keypair.bin`.
- Dev mode: `$XDG_DATA_HOME/brahman/init-keypair.bin`
`$HOME/.local/share/brahman/init-keypair.bin`
`/tmp/brahman-init-keypair.bin` (último recurso).
- **`BRAHMAN_BOOTSTRAP_PEERS`** — lista coma-separada de multiaddrs
para dial-ear al arranque y entrar al DHT. Sin esto, el nodo
arranca aislado hasta que alguien se conecte a él.
Comportamiento al activarse:
1. `keypair_store::load_or_generate(path)` carga la keypair de disco
o genera+persiste una nueva (32 bytes raw, permisos 0o600,
atomic rename). Reboots conservan el `peer_id`.
2. `BrahmanNet::with_keypair(kp)` arma el swarm con esa identidad.
3. `net.listen(multiaddr)` espera dirección resuelta y la loggea.
4. `BRAHMAN_BOOTSTRAP_PEERS` (si set) → dial a cada multiaddr.
5. El handshake server se levanta con `ServerConfig.net = Some(net)`,
que activa `announce_outputs` automático en el DHT por cada Card
con outputs.
6. Además del Unix accept loop (existing), se monta un libp2p accept
loop sobre el mismo `Server` compartido. Sesiones locales y
remotas conviven en las mismas tablas (sessions, push_table,
broker, last_matches).
Refactor del Unix accept loop: antes consumía el server vía
`server.run().await`; ahora usa `Arc<Server>::accept_one().await` en
loop para coexistir con el libp2p accept loop sin moverse el server.
Degradación grácil en cada paso: si la keypair no carga, si el
multiaddr es inválido, si el listen falla, si el bootstrap dial
revienta — loggeamos y seguimos en modo Unix-only. La doctrina de
PID 1 ("ningún subsistema opcional rompe el bucle primordial") se
mantiene.
Tests: 4 unit en `keypair_store`:
- `generate_persist_and_reload_yields_same_peer_id` — peer_id estable
across reloads (la propiedad fundamental).
- `rejects_corrupted_file` — archivo de tamaño incorrecto rechazado.
- `persisted_file_is_owner_only` — permisos 0o600 verificados.
- `default_path_honors_env``BRAHMAN_KEYPAIR_PATH` override
respeta tanto dev como root mode.
Ente-zero compila clean. Ningún test del workspace regresa.
Lo que esto desbloquea hoy:
- Para activar Arje como nodo público, basta:
```sh
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero
```
- Cualquier consumer (en otra máquina) puede luego dial-ar a ese
multiaddr + descubrir Cards anunciadas via DHT + abrir handshake
remoto firmado.
- La identidad del nodo (su `peer_id`) sobrevive reboots, así que
los nodos remotos pueden cachear "este peer_id es Arje en
máquina X" sin invalidarse cada vez.
Pendientes futuros:
- `stop_providing` al cleanup de sesión (records DHT con TTL ~24h).
- Allowlist/Denylist de peers (PKI explícito).
- Rotación de keypair sin perder peer_id (multi-key identity).
### feat(brahman-handshake): Fase 3 — trust remoto vía firma Ed25519 anclada al peer libp2p
Cuarto y último paso del plan "el encuentro entre Entes no se
restringe a local". Cierra la falla de seguridad que dejaba la red