Cluster NATS routes (auth + mutual TLS), Store/blobstore interfaces with
replicated JetStream KV and Object Store backends, idempotent
migrate-to-kv with backup, client failover over seed/control-plane lists,
replicated nonce store (closes the multi-node replay hole), and the
per-subject membership ACL (audit H4 residual). All behind the
`decentralized` flag (off); single-node SQLite+disk behavior unchanged.
The multi-node deploy (0003f) is the human's; runbook in report 0006.
Bus de mensajería unificado sobre NATS+JetStream con cifrado E2E por room (megolm/olm reducido): service de membresía/claves, librería cliente y peers demo.
unibus es un bus de mensajería unificado sobre NATS + JetStream. Una capa fina
encima de NATS aporta lo que NATS no tiene: membresía de rooms, invitaciones con
reparto de clave, y cifrado extremo a extremo (E2E) del payload por room, con
rotación de clave activa (forward secrecy) al expulsar a un miembro.
Todos los participantes —procesos/workers, interfaces de chat humanas, agentes
LLM— son peers de primera clase que hablan el mismo protocolo y se diferencian
solo por las rooms a las que se unen y por lo que hacen con lo que reciben.
Piezas
Componente
Ruta
Rol
membershipd
cmd/membershipd
Service (control plane): metadata de rooms, directorio de miembros, reparto de claves selladas, object store de media. Arranca NATS embebido si no se pasa --nats-url.
librería cliente
pkg/client
La API que consume cualquier peer. Crear/unirse a rooms, invitar, publicar/suscribir, request/reply, kick con rotación de clave, media cifrada.
worker
cmd/worker
Peer demo: publica un contador incremental cada segundo a una room cleartext.
chat
cmd/chat
Peer demo: suscriptor en vivo (modo simple) + demo de cifrado E2E y forward secrecy (--demo-encrypted).
Dos planos
Control plane: HTTP autoritativo en membershipd. Quién está en cada room,
sus claves públicas, y la clave de room K sellada por epoch para cada miembro.
Data plane: NATS. Los mensajes (frames) viajan por subject; los blobs de
media NO viajan por el bus, se cifran y se suben al object store, y por NATS
solo viaja una referencia (hash + nonce).
Criptografía (importada del registry, NO reescrita)
El cifrado E2E se compone de las 7 primitivas del capability group
e2e-messaging (docs/capabilities/e2e-messaging.md), importadas del paquete
fn-registry/functions/cybersecurity. Esta app no reimplementa ninguna
primitiva criptográfica.
Ejemplo
Demo end-to-end con go run (NATS embebido, nada que instalar):
cd projects/message_bus/apps/unibus
# 1. Service de membresía/claves (NATS embebido en :4250, HTTP en :8470)
go run ./cmd/membershipd
# 2. En otra terminal: peer publicador (proceso) — publica ticks cada 1s
go run ./cmd/worker
# 3. En otra terminal: peer suscriptor (chat humano) — imprime cada tick en vivo
go run ./cmd/chat
# 4. Demo de cifrado E2E + forward secrecy (contra el membershipd ya corriendo):# A crea room cifrada, invita a B, A publica (B descifra), A expulsa a B,# A publica de nuevo en el nuevo epoch (B ya NO puede descifrar).
go run ./cmd/chat --demo-encrypted
Para apuntar a un NATS externo en producción: --nats-url nats://host:4222 en
membershipd, worker y chat.
Cuando usarla
Cuando necesites un tejido de mensajería donde procesos, humanos y agentes LLM
sean peers uniformes (mismo protocolo, distinta política por room).
Cuando quieras rooms cifradas E2E con forward secrecy (paridad con Matrix) sin
montar un Synapse: room.ModeMatrix.
Cuando quieras fan-out cleartext rápido para telemetría/coordinación de
procesos: room.ModeNATS.
Como sustituto de la capa de transporte Matrix de agents_and_robots (fase
posterior; v1 valida el bus de forma autónoma).
Gotchas
El service NO está endurecido (v1). No hay TLS, ni rate-limit, ni auth en
las rutas GET de lectura. Confía en la red interna. Las rutas mutantes
(/rooms, /invite, /rekey) sí exigen firma Ed25519 del owner sobre los
bytes canónicos de la request. Endurecer es fase posterior.
Identidad = secreto crítico. El archivo de identidad (worker.id,
chat.id) contiene las claves privadas (Ed25519 + X25519). Se escribe 0600.
Perderlo = mensajes ilegibles, sin recuperación. Trátalo como una clave SSH.
Las rooms reciben un ULID fresco al crearse. No hay "crear o unirse por
nombre": cada CreateRoom produce un room nuevo. Los peers demo cleartext
comparten el subject (NATS enruta por subject), así que worker→chat funcionan
aunque cada uno tenga su propio room id mapeado al mismo subject.
La media no viaja por el bus.PublishMedia cifra, sube al object store y
publica solo un BlobRef. El receptor, si ve Frame.Blob != nil, descarga y
descifra con FetchMedia. El frame de media NO lleva payload inline (su nonce
vive en BlobRef.Nonce); Subscribe no intenta descifrar payloads vacíos.
Forward secrecy depende del rekey.Kick rota K a un epoch nuevo y la
re-sella solo para los miembros restantes. El expulsado pierde acceso a los
mensajes publicados después del kick, pero conserva los anteriores (las claves
de epochs pasados no se borran: cifraban datos que ya podía leer).
NATS embebido escribe JetStream en disco.--nats-store apunta a
local_files/jetstream; borrarlo resetea el historial persistido.
Build sin CGO. Usa el driver modernc.org/sqlite (pure-Go) y el paquete
cybersecurity del registry compila limpio con CGO_ENABLED=0. NO requiere
fts5 ni gcc.
Convención de subjects
proc.<svc>.<canal> telemetría/coordinación de procesos (proc.test.ticks)
rpc.<svc> request/reply (rpc.indexer)
room.<grupo> chat humano/grupo (room.general)
agent.<nombre>.{in,out} inbox/outbox de agente LLM (agent.scout.in)
Capability growth log
v0.6.0 (2026-06-07) — descentralización / alta disponibilidad (issue 0003,
fases 0003a–0003e), report 0006. El servidor NATS embebido gana soporte de
cluster con routes autenticadas (secreto de cluster) y TLS mutuo de nodo
(pkg/embeddednats.ClusterConfig + busauth.RouteTLSConfig, reusando la CA
del 0001). El control plane (pkg/membership.Store) pasa a interfaz por
branch-by-abstraction: sqliteStore (default) + jetstreamStore nuevo sobre
JetStream KV replicado (réplicas configurables R1→R3), con IsAuthorized
fail-closed ante pérdida de quorum. membershipd migrate-to-kv mueve el
estado SQLite→KV de forma idempotente con backup previo. Los blobs
(pkg/blobstore.Store, ahora interfaz) ganan un backend NATS Object Store
replicado además del disco. El cliente acepta listas de seeds NATS y de
control planes con failover/reconnect nativo, el anti-replay pasa a un store
de nonces compartido en KV con TTL (cierra el agujero de replay multi-nodo), y
se implementa la ACL por subject derivada de pertenencia (audit H4 residual:
busauth.NewNkeyAuthenticatorACL + membership.SubjectACLFor +
client.RefreshSession). Todo viaja detrás del flag decentralized (off):
el comportamiento de un solo nodo (SQLite + disco) no cambia y master sigue
verde. El despliegue multi-nodo real (0003f) lo ejecuta el humano.
v0.5.0 (2026-06-07) — hardening de seguridad (issue 0004) que cierra los
hallazgos de la auditoría red-team (report 0004) y lleva el veredicto de
exposición pública de "NO" a "sí-con-condiciones". Anti-DoS pre-auth
(http.MaxBytesReader por ruta + rechazo por Content-Length + rate-limit
por IP + MaxHeaderBytes); guard de fail-open que prohíbe arrancar con bind
público o TLS sin --bus-auth enforce; autorización por pertenencia en los GET
de room (metadata y clave sellada solo para miembros / el propio endpoint);
rooms cleartext deshabilitadas en bind público (contenido siempre E2E, mínimo
defensivo del data plane mientras la ACL por subject llega con 0003); TLS en el
control plane HTTP con la CA propia y cliente que exige https cuando hay CA;
y los medios H6/H7/H12 (owner ligado al firmante, IsAuthorized antes del
nonce-cache con poda O(expired) + cap, errores genéricos al cliente). Cada
hallazgo lleva su test adversarial TestAudit_* portado como regresión.
v0.4.0 (2026-06-07) — descubrimiento de rooms: GET /members/{endpoint}/rooms
lista las rooms de un endpoint con su metadata y rol, y client.ListMyRooms()
lo consume. El control plane es pull (no hay push de invitaciones), así que un
peer recién invitado a una room cifrada la descubre por polling y luego hace
Join + Subscribe. Pieza base para que los bots de agents_and_robots
hablen por el bus en vez de Matrix (modelo "todo son rooms", E2E).
v0.3.0 (2026-06-06) — membershipd se convierte en service de verdad: flag
--bind (default 127.0.0.1) que gobierna a la vez el HTTP de control y el NATS
embebido (embeddednats.StartHost), de modo que con --bind 0.0.0.0 un
teléfono o PC de la LAN conecta a ambos planos. Se añade un systemd-user unit
(deploy/unibus-membershipd.service, Restart=always) + deploy/install.sh
idempotente, y el bloque service: queda completo (systemd-user, restart
always, health /healthz). El Frame (pkg/frame) gana threading aditivo
(ThreadID, ReplyTo) y un tipo REACT, con PublishReply/React en el
cliente — la base para que bots de chat hablen por el bus (fase 2). Cambios
100% aditivos: el wire de los frames no-threaded es idéntico y los tests
existentes siguen verdes.
v0.2.0 (2026-06-03) — el playground gana un benchmark de rendimiento
(GET /api/bench, SSE): un publisher inunda una room con miles de mensajes a
N subscribers y una gráfica en vivo anima el throughput. Expone las dos
políticas como flags independientes (JetStream/Persist y encriptación
E2E/Encrypt) más tamaño de payload, de modo que se mide el coste de cada
capa (core NATS vs JetStream vs E2E vs E2E+JetStream) usando la librería
cliente real, sin reimplementar nada.