Commit Graph

22 Commits

Author SHA1 Message Date
egutierrez 92d4e4cb97 feat(membership): room discovery — GET /members/{endpoint}/rooms + ListMyRooms
A peer invited to an encrypted room needs to find it: the control plane is
pull-based (no server push of invitations), so add a discovery endpoint that
lists every room an endpoint belongs to, with the room's metadata and the
endpoint's role.

- store.ListRoomsForEndpoint: JOIN members+rooms, ordered by room id, empty
  slice (not error) for an endpoint in no rooms.
- membershipd: GET /members/{endpoint}/rooms returns {room_id, subject, epoch,
  policy, role}[].
- client.ListMyRooms + RoomRef: a bot polls this to discover and then Join +
  Subscribe rooms it was invited to.

Tests: store-level (owner in N rooms, member in one, unknown endpoint → []) and
client-level e2e through the embedded harness (B discovers a room A invited it
to, without prior knowledge of the room id; owner sees role=owner).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:07:34 +02:00
egutierrez ab4b099ab1 merge: cliente web + móvil de unibus
SPA de chat (web/, React+Vite+Mantine v9) contra el gateway, y app Android
nativa (android/, Kotlin+Compose) sobre el binding gomobile, con E2E en el
dispositivo. Amplía el binding (Card/Invite/Kick) y el gateway (rooms/members
+ CORS). Verificado end-to-end: chat cifrado en vivo entre dos pestañas web y
envío/recepción en el AVD Pixel_API34. Ver reports/0002.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:43:31 +02:00
egutierrez a11d67cf70 feat(android): app Kotlin/Compose sobre el binding gomobile
Cliente móvil nativo: embebe un peer real del bus (unibus.aar), de modo que
el cifrado E2E y el transporte NATS corren en el dispositivo.

- Conexión: Host (control plane) + NATS (data plane) + identidad; defaults
  10.0.2.2 para el emulador, configurables (sin IPs hardcodeadas).
- BusViewModel: llamadas de red del binding en Dispatchers.IO; los frames
  entrantes (FrameListener.onFrame, hilo NATS) se publican en un StateFlow
  thread-safe que Compose recolecta en el hilo principal.
- Chat: crear/unir room (toggle cifrado), enviar, recibir.
- El .aar es artefacto (gitignored); se regenera con gomobile bind (README).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:43:10 +02:00
egutierrez d33ca6278a feat(web): SPA de chat (React + Vite + Mantine v9)
Cliente web sobre el gateway (REST + SSE). El navegador no habla NATS ni
cripto: el peer Go del gateway lo hace.

- Pantalla de conexión: gateway URL + identidad (persistidas en localStorage).
- Navbar: crear room (con toggle de cifrado E2E), unirse por id, lista de rooms.
- Centro: mensajes en vivo por SSE, burbujas con autor y hora, composer.
- Lateral: miembros (rol owner), invitar por peer conectado, expulsar (owner).
- Mantine v9 (createTheme + MantineProvider), @tabler/icons-react, layout con
  AppShell/Stack/Group; sin Tailwind ni CSS manual. React 19 (peer dep de v9).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:43:10 +02:00
egutierrez 915f926136 feat(playground): endpoints rooms/members + CORS para la SPA
Madura el gateway web para servir a una SPA en otro origen:
- GET /api/rooms?peer=: rooms que conoce un peer (creadas o unidas).
- GET /api/members?room_id=: proxy al control plane (endpoint + rol).
- withCORS: middleware con preflight OPTIONS y headers permisivos para el
  dev server de Vite (mismo modelo de confianza de red que el control plane).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:42:56 +02:00
egutierrez 12fc77f25a feat(mobile): Card/Invite/Kick en el binding gomobile
Añade al binding plano sobre pkg/client:
- Card(): exporta la identidad pública del peer (id + sign_pub + kex_pub)
  como JSON portable, para intercambio peer-a-peer (paste/QR) sin gateway.
- Invite(roomID, peerCard): parsea una Card y sella la clave de room al
  invitado (delega en client.Invite).
- Kick(roomID, endpointID): expulsa y rota la clave (forward secrecy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:42:56 +02:00
egutierrez 69079d17d5 docs(app): bump to 0.3.0 — service hardening + frame threading growth log
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:30:55 +02:00
egutierrez 22092834bd feat(frame): additive threading — ThreadID, ReplyTo + REACT type
Chat bots need replies, threads and reactions. Add two optional, omitempty
envelope fields (ThreadID, ReplyTo) plus a REACT frame type. The fields ride the
cleartext envelope (message-id references, not secret content) and are omitted
when unset, so non-threaded frames are byte-for-byte identical on the wire and
their signatures unchanged — a non-breaking, additive change.

Client gains PublishReply (threaded reply) and React (emoji reaction). The
reaction content travels in the payload, so it is sealed like any message and
stays confidential in E2E rooms; receivers dispatch on Frame.Type == REACT and
read Frame.ReplyTo for the target. Publish is refactored to share one
publishFrame path with the new helpers; its behavior is unchanged.

Tests: frame round-trip of a threaded REACT frame (golden), non-threaded
wire/sig back-compat asserting thr/re keys are absent (edge), Unmarshal of
garbage errors (error path), and an end-to-end reply+reaction round-trip in an
encrypted ModeMatrix room.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:10:44 +02:00
egutierrez b2e6712dd2 docs(app): fill service: block — systemd-user, Restart=always, LAN-reachable
membershipd now ships as a systemd user service (unit unibus-membershipd.service,
restart_policy always, runtime systemd-user). is_local_only flips to false since
--bind 0.0.0.0 makes both planes LAN-reachable. fn doctor services-spec: OK, no
drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:06:33 +02:00
egutierrez f6b53620e9 feat(deploy): systemd user unit + install script for membershipd
Add deploy/unibus-membershipd.service (Restart=always, binds both planes to
0.0.0.0 for LAN reachability), an idempotent deploy/install.sh that builds the
binary, symlinks the unit, and enables+starts it, plus deploy/README.md with
operate/health instructions.

Restart=always is deliberate: a clean SIGTERM exits 0 and Restart=on-failure
would not restart it, leaving the service silently dead (the sqlite_api gotcha).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:05:53 +02:00
egutierrez 01f8988cc3 feat(membershipd): --bind flag governs HTTP + embedded NATS interface
Add a --bind flag (default 127.0.0.1) to membershipd that controls which
network interface both the control-plane HTTP API and the embedded NATS data
plane listen on. Use 0.0.0.0 to expose the stack to the LAN so remote peers
(phones, other PCs) can connect; keep the default for a loopback-only dev stack.

embeddednats gains StartHost(storeDir, host, port) for explicit interface
control; Start stays a backward-compatible wrapper (host "" = nats default
0.0.0.0) so the playground and tests are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:05:05 +02:00
agent 0bd6eb5315 feat(mobile): gomobile-friendly wrapper over pkg/client for Android binding 2026-06-05 17:40:28 +02:00
egutierrez 7aad903236 chore: auto-commit (1 archivos)
- mobile/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 17:34:22 +02:00
egutierrez 9f05aa1a2a merge: benchmark de rendimiento en el playground de unibus (v0.2.0) 2026-06-03 22:33:26 +02:00
Egutierrez 6b162deeb0 feat(playground): benchmark de rendimiento con flags JetStream/E2E/payload
Añade GET /api/bench (SSE) y una seccion de simulador en index.html: un publisher
inunda una room con miles de mensajes a N subscribers y una grafica en vivo anima
el throughput. Las dos politicas de room se exponen como flags independientes
(persist=JetStream, encrypt=E2E AEAD+Ed25519) mas tamano de payload, midiendo el
coste de cada capa con la libreria cliente real. El benchmark usa peers efimeros
propios, sin tocar los peers nombrados del sandbox manual.

Verificado: las 4 combinaciones enc x persist con fan-out exacto. Bump app v0.2.0.
2026-06-03 22:33:26 +02:00
agent 8c680bc002 feat: optional per-room JetStream persistence (history + offline replay), gated by RoomPolicy.Persist 2026-06-03 21:48:55 +02:00
fn-registry agent bb2e412744 chore: sync from fn-registry agent 2026-06-03 21:37:25 +02:00
agent b1d1f64c16 fix: surface clear error when joining encrypted room without invitation
- membership server returns 403 + human-readable message on missing sealed key (was leaking 'sql: no rows in result set')
- client doJSON unwraps the server's {"error"} field instead of pasting the raw HTTP envelope
2026-06-03 21:33:42 +02:00
agent c203ba63e6 chore: remove dead roomFor method in playground server 2026-06-03 21:19:48 +02:00
agent 22d6106e32 feat: add web playground for interactive bus testing (SSE + vanilla UI, all-in-one server) 2026-06-03 21:18:27 +02:00
agent 888ff75236 fix: default ports 8470 (HTTP) / 4250 (NATS) to avoid clash with registry_api on 8420 2026-06-03 19:54:36 +02:00
agent cd02a52191 feat: initial scaffold of unibus message bus (membership service + client lib + demo peers) 2026-06-03 19:47:32 +02:00