arisa 5.1.2 → 5.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +12 -4
  2. package/ARISA-MASTER-SLAVE-SPEC.md +844 -0
  3. package/README.md +25 -0
  4. package/package.json +1 -1
  5. package/src/core/agent/agent-manager.js +55 -12
  6. package/src/core/config/config-defaults.js +3 -1
  7. package/src/core/tools/daemon-processes.js +48 -6
  8. package/src/core/tools/daemon-runtime.js +370 -138
  9. package/src/core/tools/ipc-client.js +3 -0
  10. package/src/core/tools/official-tool-catalog.js +32 -0
  11. package/src/core/tools/official-tool-installer.js +183 -0
  12. package/src/core/tools/tool-registry.js +203 -18
  13. package/src/core/tools/tool-resource-note-store.js +78 -0
  14. package/src/index.js +25 -2
  15. package/src/official-tools.lock.json +40 -0
  16. package/src/runtime/arisa-capabilities.js +43 -3
  17. package/src/runtime/create-app.js +9 -0
  18. package/src/runtime/create-headless-app.js +77 -0
  19. package/src/runtime/doctor.js +27 -2
  20. package/src/runtime/headless-tool-executor.js +45 -0
  21. package/src/runtime/paths.js +16 -4
  22. package/src/runtime/secure-request-file.js +21 -0
  23. package/src/runtime/slave-bootstrap-url.js +51 -0
  24. package/src/runtime/slave-cli.js +267 -0
  25. package/src/runtime/slave-service.js +225 -0
  26. package/src/runtime/tool-usage-report.js +11 -3
  27. package/src/transport/telegram/bot.js +37 -7
  28. package/test/capabilities-security.test.js +29 -0
  29. package/test/daemon-catalog-conformance.test.js +3 -1
  30. package/test/daemon-runtime.test.js +58 -2
  31. package/test/official-tool-installer.test.js +107 -0
  32. package/test/paths.test.js +6 -12
  33. package/test/slave-cli.test.js +282 -0
  34. package/test/telegram-text-artifact.test.js +24 -1
  35. package/test/tool-capability-search.test.js +55 -0
  36. package/test/tool-registry-run.test.js +70 -1
  37. package/test/tool-resource-note.test.js +50 -0
  38. package/test/tool-usage.test.js +10 -5
  39. package/test-fixtures/fake-daemon.js +12 -1
@@ -0,0 +1,844 @@
1
+ # Arisa Master/Slave — Especificación de trabajo
2
+
3
+ Estado: contrato de implementación v1 aprobado el 13 de agosto de 2026.
4
+
5
+ Las decisiones y límites de este documento son normativos para la versión 1.
6
+ Los cambios de contrato posteriores requieren una revisión explícita de esta
7
+ especificación.
8
+
9
+ ## Objetivo
10
+
11
+ Permitir que una instalación de Arisa opere otros servidores mediante pequeños
12
+ procesos remotos de Arisa. El usuario habla únicamente con la instalación
13
+ inteligente de Arisa. Un servidor remoto no necesita Telegram, Pi Agent,
14
+ credenciales de Codex ni su propia sesión de razonamiento.
15
+
16
+ La configuración buscada consiste en pedirle a Master una URL de bootstrap y
17
+ pegar un solo comando en el servidor Slave. La URL contiene la IP y el puerto de
18
+ Master más un secreto de uso único. Después del enrolamiento, Arisa puede
19
+ inspeccionar el servidor, usar las herramientas que tenga instaladas, leer
20
+ archivos y ejecutar comandos autorizados.
21
+
22
+ ## Nombres
23
+
24
+ Los nombres canónicos del producto son:
25
+
26
+ - **Arisa Master**: la instalación inteligente conectada con Telegram y Pi
27
+ Agent. Decide qué debe ocurrir y dónde.
28
+ - **Arisa Slave**: un ejecutor remoto determinista. Informa sus capacidades y
29
+ realiza las solicitudes de su Arisa Master emparejada.
30
+
31
+ Los roles se llaman `master` y `slave` de forma consistente en la configuración,
32
+ el protocolo, la CLI y la documentación.
33
+
34
+ ## Decisiones de diseño confirmadas
35
+
36
+ - El transporte es TCP directo mediante `node:net` y la criptografía se
37
+ implementa con `node:crypto`.
38
+ - Arisa Master escucha en un `ip:port` explícito y Arisa Slave se conecta hacia
39
+ afuera directamente mediante TCP.
40
+ - Slave fija como endpoint la IP literal incluida en la URL de bootstrap. Sólo
41
+ acepta órdenes dentro de esa conexión autenticada con Master.
42
+ - La conexión se autentica con un secreto aleatorio de alta entropía y cifra
43
+ todos los mensajes en la capa de protocolo propia de Arisa.
44
+ - La máquina de Master debe ser alcanzable desde Slave mediante una IP pública,
45
+ una red privada o una VPN. La apertura y el forwarding del puerto de Master
46
+ quedan fuera de Arisa. Slave no abre puertos de entrada.
47
+ - Arisa Slave puede ejecutarse con un usuario dedicado, con el usuario actual o
48
+ como `root`; el usuario debe elegirlo explícitamente.
49
+ - Arisa Master es dueña de todo el razonamiento. Arisa Slave nunca elige
50
+ herramientas a partir de una intención ni ejecuta un modelo.
51
+ - Una Slave puede instalar y exponer herramientas normales de Arisa.
52
+ - La gestión de Master/Slave vive en una tool daemon global, con el nombre de
53
+ trabajo `master-slave`, y no en Arisa Core.
54
+ - El runtime general de daemons usa IPC local inmediato con streaming y conserva
55
+ un journal durable para recuperación; no depende de polling durante la
56
+ operación normal.
57
+ - Master puede organizar Slaves en grupos muchos-a-muchos y ejecutar una misma
58
+ operación sobre una o varias Slaves o grupos.
59
+
60
+ ## Perfil operativo v1
61
+
62
+ - Los transcripts criptográficos usan campos binarios en orden fijo, cada uno
63
+ prefijado por su longitud, y el separador de dominio
64
+ `arisa-master-slave-handshake-v1`.
65
+ - Los secretos de conexión vencen a los 10 minutos.
66
+ - Un frame puede ocupar como máximo 1 MiB y la salida acumulada de un job puede
67
+ ocupar como máximo 16 MiB.
68
+ - La reconexión usa backoff exponencial con jitter entre 1 y 60 segundos.
69
+ - Una Slave desconectada durante 5 minutos genera el aviso offline deduplicado.
70
+ - Tanto la dirección de binding como el endpoint que Master publica son
71
+ configuración obligatoria. No tienen un valor predeterminado implícito.
72
+ - Un batch ejecuta como máximo cuatro jobs simultáneos. Cada stream admite como
73
+ máximo 1 MiB pendiente antes de aplicar backpressure.
74
+ - La tool oficial se obtiene desde un commit inmutable y cada archivo se valida
75
+ contra un manifiesto SHA-256 incluido en la misma versión de Arisa Core.
76
+ - Linux con systemd es la primera plataforma operativa soportada. Master y Slave
77
+ pueden coexistir en un host mediante homes, sockets, PID y logs separados.
78
+ - El usuario dedicado es la opción recomendada. El usuario actual está
79
+ permitido y root requiere siempre una elección y confirmación explícitas.
80
+ - Los jobs dirigidos a Slaves offline se rechazan en v1.
81
+ - La instalación remota acepta sólo tools del catálogo verificado y requiere
82
+ confirmación. Nunca se instala código silenciosamente como root.
83
+ - `arisa slave status` incluye el diagnóstico equivalente a Doctor para el host
84
+ headless. `arisa slave log` muestra el log del host Slave.
85
+
86
+ ## Arquitectura
87
+
88
+ ```text
89
+ Telegram
90
+
91
+
92
+ Arisa Core
93
+ - Pi Agent / Codex
94
+ - conversaciones
95
+ - autorización por chat
96
+ - selección de operaciones remotas
97
+ │ llamada local de tool
98
+
99
+ Tool daemon master-slave — rol Master
100
+ - listener, identidades y conexiones
101
+ - registro y grupos de Slaves
102
+ - jobs remotos y auditoría
103
+ ▲ escucha TCP en ip:port
104
+ │ conexión saliente cifrada
105
+ Tool daemon master-slave — rol Slave
106
+ - identidad y política
107
+ - inventario del host
108
+ - ejecutor determinista de jobs
109
+ - almacenamiento local de jobs y auditoría
110
+ │ IPC local
111
+
112
+ Arisa headless
113
+ - supervisor de daemons
114
+ - ToolRegistry local
115
+ - herramientas instaladas
116
+ ```
117
+
118
+ La conexión entre Master y Slave es directa. Slave conoce la dirección de Master
119
+ y es siempre quien inicia o restablece la conexión. Master acepta solamente el
120
+ protocolo binario autenticado de Arisa en el puerto configurado. Slave no expone
121
+ ningún puerto ni publica su IPC Unix local.
122
+
123
+ ## Tool daemon y límite de Arisa Core
124
+
125
+ La tool daemon `master-slave` contiene los dos roles del protocolo. En una
126
+ instalación Master mantiene el listener y todas las sesiones entrantes. En una
127
+ instalación Slave mantiene una sola conexión saliente con su Master. El proceso
128
+ es global porque representa infraestructura de la instalación, no el estado de
129
+ un chat individual.
130
+
131
+ La tool es dueña de:
132
+
133
+ - transporte TCP, framing, cifrado e identidades emparejadas;
134
+ - secretos de conexión, perfiles, grupos y estado de las Slaves;
135
+ - dispatch, streaming, cancelación y auditoría de jobs remotos;
136
+ - autorización remota resultante de las concesiones emitidas por Master.
137
+
138
+ Arisa Core conserva únicamente responsabilidades genéricas:
139
+
140
+ - descubrimiento y ejecución de tools;
141
+ - supervisor, salud y ciclo de vida de daemons;
142
+ - IPC local, artifacts y eventos para el agente;
143
+ - el adaptador CLI `arisa slave <url>`.
144
+
145
+ En el servidor Slave se inicia un host headless de Arisa con supervisor, IPC y
146
+ ToolRegistry, pero sin Telegram ni Pi Agent. La tool daemon usa exclusivamente el
147
+ IPC público de Arisa para listar y ejecutar otras tools; no importa ni alcanza
148
+ internals de Core.
149
+
150
+ Como las tools no forman parte del paquete de Core, el primer
151
+ `arisa slave <url>` instala automáticamente la versión oficial verificada de
152
+ `master-slave`, inicia su daemon y le entrega la solicitud mediante un archivo
153
+ temporal con permisos restrictivos. Las ejecuciones posteriores reutilizan la
154
+ tool instalada. El secreto no se copia a variables de entorno, logs ni metadata
155
+ de arranque del daemon.
156
+
157
+ ## Experiencia de usuario
158
+
159
+ El usuario le dice a Arisa Master que quiere agregar una Slave. Master crea un
160
+ secreto aleatorio de 256 bits, de vida corta y uso único, lo asocia con el chat
161
+ solicitante y devuelve una sola línea:
162
+
163
+ ```bash
164
+ npm i -g arisa && arisa slave tcp://198.51.100.12:4719/arisa_secret_v1_<BASE64URL>
165
+ ```
166
+
167
+ La URL completa es el único parámetro de `arisa slave`. Su parser exige:
168
+
169
+ - esquema exacto `tcp`;
170
+ - IP literal IPv4 o IPv6, con IPv6 entre corchetes;
171
+ - puerto explícito;
172
+ - exactamente un segmento de path con el secreto;
173
+ - ausencia de usuario, query, fragmento y segmentos adicionales.
174
+
175
+ En esta especificación se lo llama **secreto de conexión**, no hash secreto. Un
176
+ hash corto puede mostrarse como fingerprint, pero el protocolo necesita el
177
+ secreto aleatorio completo para autenticar el primer contacto y derivar claves.
178
+ La URL es sensible, no debe registrarse, y deja de servir cuando se consume o
179
+ vence. La simplicidad de pasarla como argumento implica que puede quedar en el
180
+ historial del shell; la implementación debe advertirlo al mostrar el comando.
181
+
182
+ Al ejecutar el comando, Slave:
183
+
184
+ 1. parsea y valida la URL;
185
+ 2. crea su identidad local y detecta el usuario de ejecución;
186
+ 3. si corre con UID 0, pide confirmar si debe continuar como root o instalar el
187
+ servicio con un usuario dedicado;
188
+ 4. abre una conexión saliente con la IP y el puerto de Master;
189
+ 5. completa el handshake usando el secreto;
190
+ 6. intercambia automáticamente identidades, versión, perfil, política,
191
+ capacidades y catálogo de herramientas;
192
+ 7. se registra como servicio y mantiene la reconexión automática.
193
+
194
+ Master propone desde Telegram el nombre, propósito, raíces y permisos después
195
+ del primer contacto. El usuario los confirma conversacionalmente; no hacen falta
196
+ más parámetros de red en la consola de Slave. También puede asignarla a uno o
197
+ varios grupos en ese momento o reorganizarla después desde la conversación.
198
+
199
+ ## Perfil y descubrimiento de Slave
200
+
201
+ Una Slave publica únicamente los metadatos operativos necesarios para tomar
202
+ decisiones correctas:
203
+
204
+ ```json
205
+ {
206
+ "slaveId": "uuid",
207
+ "name": "production-api",
208
+ "description": "Ejecuta la API pública y sus workers",
209
+ "hostname": "api-01",
210
+ "platform": "linux",
211
+ "arch": "arm64",
212
+ "arisaVersion": "x.y.z",
213
+ "masterEndpoint": "tcp://198.51.100.12:4719",
214
+ "privilege": {
215
+ "user": "arisa-slave",
216
+ "root": false,
217
+ "scope": "restricted"
218
+ },
219
+ "roots": ["/srv/api"],
220
+ "capabilities": ["inspect", "read", "tool.run", "exec"],
221
+ "tools": []
222
+ }
223
+ ```
224
+
225
+ El inventario puede incluir CPU, memoria, discos, runtimes relevantes y salud
226
+ de servicios. No debe incluir variables de entorno, credenciales, configuración
227
+ de herramientas, contenido de archivos, historial del shell ni otros secretos.
228
+
229
+ Master obtiene los perfiles de las Slaves dinámicamente mediante operaciones de
230
+ la tool como `list_slaves` e `inspect_slave`. Los perfiles completos no se
231
+ inyectan en cada prompt de Pi.
232
+
233
+ El registro de Master conserva además `connectionState`, `connectedAt`,
234
+ `disconnectedAt` y la causa del último cierre observado. Los umbrales de tiempo
235
+ offline para avisar al usuario pertenecen a la configuración centralizada y no
236
+ se codifican como constantes locales.
237
+
238
+ ## Grupos de Slaves
239
+
240
+ Master puede organizar Slaves mediante grupos con nombre. Los grupos son
241
+ metadata local de Master: no cambian la identidad, conexión ni política propia
242
+ de una Slave y no se envían como autoridad al servidor remoto.
243
+
244
+ La pertenencia es muchos-a-muchos. Una Slave puede integrar varios grupos y un
245
+ grupo puede contener cualquier cantidad de Slaves. Cada grupo tiene un
246
+ `groupId` inmutable, un nombre modificable, una descripción opcional y la lista
247
+ de `slaveId` asociados. Los nombres son únicos dentro del chat propietario; las
248
+ operaciones y referencias persistidas usan siempre `groupId`.
249
+
250
+ Ejemplo:
251
+
252
+ ```text
253
+ grupo X: api-x-1, api-x-2, worker-x-1
254
+ grupo Y: api-y-1, worker-y-1
255
+ producción: api-x-1, api-x-2, api-y-1
256
+ ```
257
+
258
+ Las operaciones remotas reciben un selector común:
259
+
260
+ ```json
261
+ {
262
+ "target": {
263
+ "slaveIds": ["slave-uuid-1"],
264
+ "groupIds": ["group-uuid-x", "group-uuid-y"]
265
+ }
266
+ }
267
+ ```
268
+
269
+ Master resuelve la unión de ambos conjuntos y elimina Slaves repetidas antes de
270
+ crear jobs. La membresía se convierte en un snapshot al comenzar: agregar o
271
+ quitar una Slave después no modifica un batch ya aceptado.
272
+
273
+ Una ejecución grupal crea un `batchId` y un job hijo independiente por Slave.
274
+ La concurrencia máxima pertenece a la configuración centralizada. Los chunks de
275
+ salida pueden intercalarse entre Slaves, pero cada chunk incluye `slaveId`, nombre
276
+ y secuencia; dentro de una Slave mantienen su orden original. El resultado final
277
+ muestra cada servidor y un resumen de completados, fallidos, cancelados y no
278
+ iniciados.
279
+
280
+ Antes de producir efectos, Master verifica que todas las Slaves seleccionadas
281
+ estén autorizadas para el chat y publiquen la capacidad solicitada. La operación
282
+ segura predeterminada es no iniciar el batch si falla este preflight. El usuario
283
+ puede pedir explícitamente ejecución parcial; una vez iniciado no existe una
284
+ transacción distribuida y una falla en una Slave no revierte efectos ya
285
+ completados en otras.
286
+
287
+ Cancelar un batch impide iniciar sus jobs pendientes y solicita cancelación a
288
+ los que estén activos. Los jobs que ya terminaron conservan su resultado. Las
289
+ operaciones sensibles muestran antes de la confirmación los grupos resueltos,
290
+ la cantidad de Slaves y cuáles operan como root.
291
+
292
+ Eliminar un grupo borra solamente esa agrupación. No revoca, desconecta ni
293
+ elimina sus Slaves y no altera batches que ya tomaron su snapshot.
294
+
295
+ ## Herramientas en Arisa Slave
296
+
297
+ Arisa Slave usa los contratos existentes de paquetes y CLI de herramientas de
298
+ Arisa. Las herramientas instaladas viven en el directorio de Arisa de la Slave
299
+ y se ejecutan localmente en ese servidor. Slave no necesita Pi Agent para
300
+ cargarlas ni ejecutarlas.
301
+
302
+ Slave publica un catálogo seguro que contiene, para cada herramienta instalada:
303
+
304
+ - nombre, versión y digest del paquete;
305
+ - descripción, categoría y keywords;
306
+ - declaraciones de entrada y salida;
307
+ - nombres de campos del esquema de configuración, nunca sus valores;
308
+ - requisitos declarados de sistema de archivos, procesos, red y privilegios;
309
+ - estado de disponibilidad y salud.
310
+
311
+ Arisa Master decide qué capacidad remota usar. Slave no reinterpreta
312
+ silenciosamente una operación ni sustituye una herramienta por otra.
313
+
314
+ Por ejemplo, si una Slave publica una herramienta `trash` y el usuario le pide a
315
+ Master que quite un archivo de forma segura, Pi puede elegir:
316
+
317
+ ```json
318
+ {
319
+ "operation": "tool.run",
320
+ "target": {
321
+ "slaveIds": ["slave-uuid"],
322
+ "groupIds": []
323
+ },
324
+ "tool": "trash",
325
+ "args": { "path": "/srv/api/old.log" }
326
+ }
327
+ ```
328
+
329
+ `trash` es solamente un ejemplo de selección genérica de una herramienta
330
+ remota. El protocolo de Slave no contiene ninguna regla específica para
331
+ `trash`. Si Master solicita una herramienta que no está instalada, Slave
332
+ devuelve `capability_missing`; no recurre como fallback a un comando de shell ni
333
+ a un comportamiento diferente.
334
+
335
+ La tool daemon `master-slave` expone a Arisa estas operaciones:
336
+
337
+ - `create_slave_bootstrap`
338
+ - `list_slaves`
339
+ - `inspect_slave`
340
+ - `create_slave_group`
341
+ - `list_slave_groups`
342
+ - `add_slaves_to_group`
343
+ - `remove_slaves_from_group`
344
+ - `delete_slave_group`
345
+ - `list_slave_tools`
346
+ - `run_slave_tool`
347
+ - `read_slave_file`
348
+ - `run_slave_command`
349
+ - `install_slave_tool`
350
+ - `cancel_slave_batch`
351
+ - `revoke_slave`
352
+
353
+ Las operaciones que ejecutan trabajo usan el selector común `target`; no se
354
+ duplican variantes de cada operación para una Slave y para un grupo.
355
+
356
+ La instalación remota de herramientas es un permiso independiente. Una política
357
+ de Slave puede exigir confirmación para cada instalación, permitir un subconjunto
358
+ firmado del catálogo o aceptar instalaciones únicamente desde la consola local
359
+ de Slave. La instalación de código en una Slave ejecutada como root nunca debe
360
+ autorizarse silenciosamente.
361
+
362
+ Las solicitudes de herramientas conservan `requestedByChatId` para mantener
363
+ aislados el estado y la configuración de herramientas por chat. Los resultados
364
+ de las herramientas y los archivos generados vuelven por la conexión cifrada y
365
+ se convierten en artifacts del chat solicitante en Arisa Master.
366
+
367
+ ## IPC general de daemons: rápido y durable
368
+
369
+ El runtime compartido de daemons debe ofrecer un canal local persistente mediante
370
+ socket Unix o named pipe en Windows. Es una mejora general para todas las tools
371
+ daemon, no una implementación privada de `master-slave`.
372
+
373
+ La rapidez no reemplaza la durabilidad. El flujo de cada job es:
374
+
375
+ 1. el cliente persiste atómicamente la solicitud con estado `queued`;
376
+ 2. notifica inmediatamente al daemon por el canal IPC, sin esperar un poll;
377
+ 3. el daemon reclama el `jobId` de forma atómica y persiste `accepted` antes de
378
+ producir un efecto externo;
379
+ 4. el daemon transmite eventos y chunks por IPC mientras ejecuta;
380
+ 5. el resultado final se persiste antes de confirmar `completed` o `failed`.
381
+
382
+ El daemon escanea el journal al iniciar para recuperar solicitudes `queued` o
383
+ `accepted`. Una notificación repetida sólo vuelve a señalar el mismo `jobId`; no
384
+ duplica su ejecución. Durante la operación normal no se sondea el directorio de
385
+ comandos. Así se conserva la recuperación del diseño basado en archivos y se
386
+ elimina su espera periódica.
387
+
388
+ Los mensajes locales usan frames versionados con esta forma lógica:
389
+
390
+ ```json
391
+ {
392
+ "jobId": "uuid",
393
+ "type": "accepted | progress | chunk | completed | failed",
394
+ "sequence": 1,
395
+ "payload": {}
396
+ }
397
+ ```
398
+
399
+ El canal admite varios jobs multiplexados. Cada daemon conserva su propia
400
+ política de concurrencia: streaming no implica ejecutar todo en paralelo. Si el
401
+ consumidor no puede recibir al ritmo del productor se aplica backpressure; no se
402
+ descartan chunks ni se permite crecimiento ilimitado de memoria.
403
+
404
+ El socket local vive en el directorio administrado del daemon, usa permisos
405
+ restrictivos y exige la identidad o capability token emitida por el supervisor.
406
+ No se publica fuera del host.
407
+
408
+ La invocación pública de una tool sigue siendo `run --request-file`. Para
409
+ conservar compatibilidad, ToolRegistry acepta dos formatos de salida:
410
+
411
+ - un único JSON final, como usan las tools actuales;
412
+ - NDJSON versionado con eventos `accepted`, `progress`, `chunk`, `completed` y
413
+ `failed` para tools con streaming.
414
+
415
+ Cuando el manifest declara un daemon, ToolRegistry envía el job directamente al
416
+ IPC compartido y evita crear un proceso intermediario por cada llamada. El
417
+ entrypoint `run --request-file` permanece como adaptador para invocaciones desde
418
+ la terminal y usa el mismo cliente IPC. Las tools sin daemon continúan
419
+ ejecutándose como procesos independientes.
420
+
421
+ Para las tools sin daemon que emitan NDJSON, ToolRegistry parsea `stdout`
422
+ incrementalmente y publica los eventos mediante el mismo observador genérico de
423
+ ejecución; deja de acumular toda la salida en memoria hasta que termina el
424
+ proceso. El evento terminal contiene el mismo resultado que recibe hoy el
425
+ llamador. Los transports pueden mostrar progreso sin despertar a Pi por cada
426
+ chunk y deben agrupar actualizaciones según sus propios límites. `stderr`
427
+ continúa reservado para diagnóstico y nunca se mezcla con frames de protocolo.
428
+
429
+ ## Protocolo TCP
430
+
431
+ El protocolo es propio, pero usa primitivas estándar incluidas en Node:
432
+
433
+ - `net.Socket` para el transporte;
434
+ - Ed25519 para las identidades persistentes de los pares y las firmas del
435
+ transcript;
436
+ - X25519 efímero para acordar la clave de cada sesión;
437
+ - HKDF-SHA256 para derivar claves direccionales;
438
+ - AES-256-GCM para cifrado autenticado;
439
+ - `crypto.randomBytes()` para secretos de conexión, salts y challenges.
440
+
441
+ La conexión TCP es persistente y bidireccional. `net.Socket` funciona como un
442
+ stream `Duplex`: después del handshake, Master y Slave pueden enviar frames en
443
+ cualquier momento por el mismo socket, incluso mientras reciben datos en la
444
+ dirección opuesta.
445
+
446
+ Ambos extremos configuran `socket.setNoDelay(true)` al establecer la conexión
447
+ para priorizar la latencia de órdenes y respuestas pequeñas. Como TCP transporta
448
+ un flujo de bytes y no mensajes, el parser debe admitir que un frame llegue
449
+ fragmentado o que una lectura contenga varios frames. Si `socket.write()`
450
+ devuelve `false`, el emisor pausa nuevos envíos hasta el evento `drain`, sin
451
+ descartar ni reordenar frames.
452
+
453
+ Después del handshake, los frames usan un encabezado fijo y un payload acotado:
454
+
455
+ ```text
456
+ uint32be frameLength
457
+ uint8 protocolVersion
458
+ uint8 messageType
459
+ uint64be sequence
460
+ bytes ciphertext
461
+ bytes[16] authenticationTag
462
+ ```
463
+
464
+ Cada dirección tiene una clave y un salt de nonce separados. El nonce de
465
+ AES-GCM se deriva del salt direccional y de la secuencia monotónica. La versión,
466
+ el tipo de mensaje y la secuencia se autentican como datos adicionales. Una
467
+ secuencia repetida, un tag inválido, un frame demasiado grande, una transición
468
+ inesperada o una versión incompatible cierran la conexión.
469
+
470
+ Los payloads de aplicación de la versión 1 son JSON en UTF-8. Los transcripts
471
+ criptográficos deben usar una codificación de bytes definida explícitamente en
472
+ lugar de depender del orden de las claves de un objeto JSON.
473
+
474
+ Como se trata de un protocolo de seguridad, la implementación necesita vectores
475
+ de prueba, pruebas con frames fragmentados, fuzzing de los campos de longitud,
476
+ pruebas de replay y una revisión independiente antes de habilitar la ejecución
477
+ remota.
478
+
479
+ ## Emparejamiento y reconexión
480
+
481
+ 1. Master genera un secreto de conexión aleatorio, de vida corta y uso único, y
482
+ lo asocia con el chat que pidió agregar la Slave.
483
+ 2. Master construye la URL `tcp://ip_master:port/secret` y se la entrega al
484
+ usuario dentro del comando completo de instalación.
485
+ 3. Slave parsea la URL, genera una identidad Ed25519 persistente y abre una
486
+ conexión TCP saliente con la IP literal y el puerto indicados.
487
+ 4. Slave verifica que la dirección remota real del socket coincide con la IP de
488
+ la URL y mantiene ese endpoint durante el handshake.
489
+ 5. Master y Slave intercambian sus claves públicas de identidad, claves X25519
490
+ efímeras y challenges aleatorios.
491
+ 6. Ambas prueban que poseen el secreto de conexión y firman el transcript exacto
492
+ del handshake.
493
+ 7. La salida de X25519 y el secreto de conexión se combinan mediante HKDF para
494
+ producir las claves del primer handshake.
495
+ 8. Cada parte confirma el transcript y persiste la identidad y el endpoint de su
496
+ par.
497
+ 9. Master consume y elimina el secreto inicial; ya no puede enrolar otra Slave
498
+ con ese valor. Slave elimina la URL secreta de su estado y conserva solamente
499
+ `tcp://ip_master:port` más la identidad fijada de Master.
500
+ 10. Las reconexiones futuras autentican las identidades persistentes, usan nuevas
501
+ claves X25519 efímeras y derivan claves de sesión nuevas sin reutilizar el
502
+ secreto inicial.
503
+
504
+ Slave conserva el endpoint de Master y restablece la sesión con backoff cuando
505
+ se corta. Conectar a una IP literal funciona además como allowlist de destino:
506
+ Slave sólo procesa órdenes recibidas por el socket que ella misma abrió hacia
507
+ esa IP y después de autenticar la identidad Ed25519 fijada. La IP no sustituye
508
+ el secreto inicial, la identidad, el cifrado autenticado ni la autorización del
509
+ chat.
510
+
511
+ Para IPv4 se normalizan también las direcciones IPv4-mapped IPv6 antes de
512
+ comparar. No se confía en redirects, headers, hostnames ni endpoints declarados
513
+ por el servidor. El operador puede restringir además el tráfico saliente con el
514
+ firewall del host, pero Arisa no modifica reglas de firewall automáticamente.
515
+
516
+ La revocación coordinada elimina en ambas partes la identidad aceptada y termina
517
+ la conexión activa. Si Slave está inaccesible, Master puede olvidar el registro,
518
+ pero para revocar realmente su identidad en Slave hay que ejecutar
519
+ `arisa slave unpair` localmente o reconectarla para completar la revocación. Un
520
+ nuevo emparejamiento genera un secreto y una relación de identidad nuevos; no es
521
+ un fallback implícito de recuperación.
522
+
523
+ ## Desconexión y cambio excepcional del endpoint
524
+
525
+ Slave mantiene la reconexión automática en segundo plano sin intervención de
526
+ Pi. Master conoce el estado de cada socket entrante. Cuando una Slave permanece
527
+ desconectada más allá del umbral configurado, Master emite un único evento
528
+ accionable para el chat que la administra y no repite el mismo aviso mientras
529
+ no cambie el estado.
530
+
531
+ Master no puede saber desde el lado servidor si Slave está apagada, perdió red o
532
+ tiene un endpoint incorrecto. El aviso no intenta resolver automáticamente la
533
+ causa. Primero indica revisar el servicio y la red.
534
+
535
+ La dirección de Master debe ser estable. No existe un mecanismo especial para
536
+ actualizarla. Si excepcionalmente cambia la IP o el puerto, el usuario le pide a
537
+ Master un secreto de conexión nuevo y vuelve a ejecutar en el servidor Slave el
538
+ mismo comando normal:
539
+
540
+ ```php-template
541
+ arisa slave tcp://<ip_master>:4719/<secret>
542
+ ```
543
+
544
+ Si ya existe una identidad local, Slave conserva el emparejamiento y exige que el
545
+ nuevo endpoint demuestre la misma identidad Ed25519 de Master. Sólo guarda la
546
+ nueva dirección después de completar el handshake. El secreto conserva las
547
+ mismas reglas de cualquier conexión inicial: alta entropía, vida corta y un solo
548
+ uso. Si el servicio Slave o la red están caídos, repetir el comando no resuelve
549
+ el problema y el aviso debe decirlo claramente.
550
+
551
+ ## Ejecución como root
552
+
553
+ La instalación global por npm y la ejecución de Slave son decisiones
554
+ independientes. El paquete puede instalarse globalmente con privilegios elevados
555
+ mientras que el servicio se ejecuta con un usuario dedicado.
556
+
557
+ Si `arisa slave <url>` se ejecuta con UID 0, debe presentar opciones
558
+ explícitas:
559
+
560
+ ```text
561
+ Ejecutar Arisa Slave como:
562
+ 1. usuario dedicado arisa-slave (recomendado)
563
+ 2. otro usuario existente
564
+ 3. root
565
+ ```
566
+
567
+ Se admite elegir `root`. El perfil resultante y todos los informes de Master
568
+ deben mostrar que Slave tiene autoridad root. El modo root restringido puede
569
+ seguir aplicando las raíces y operaciones configuradas en la capa de aplicación.
570
+ El modo root con host completo requiere otra confirmación explícita.
571
+
572
+ Las restricciones de aplicación reducen el uso accidental, pero dejan de ser
573
+ una frontera de seguridad si el propio proceso de Slave resulta comprometido:
574
+ una Slave root implica que Master emparejada puede controlar potencialmente el
575
+ servidor completo.
576
+
577
+ Quien quiera instalar el servicio como root ejecuta el mismo comando desde una
578
+ sesión con UID 0 y confirma esa opción. No se agregan parámetros de bootstrap
579
+ para seleccionar privilegios y el modo root nunca se infiere silenciosamente.
580
+
581
+ ## Jobs remotos
582
+
583
+ La versión 1 admite estas operaciones estructuradas:
584
+
585
+ - `slave.inspect`
586
+ - `fs.list`
587
+ - `fs.read`
588
+ - `tool.list`
589
+ - `tool.run`
590
+ - `process.exec`
591
+ - `job.cancel`
592
+
593
+ `process.exec` recibe un ejecutable, un array de argumentos, un directorio de
594
+ trabajo y un timeout. Usa `spawn(executable, argv)` sin shell. La ejecución
595
+ mediante shell es una capacidad independiente que debe habilitarse
596
+ explícitamente en la política de Slave.
597
+
598
+ Cada job incluye:
599
+
600
+ ```json
601
+ {
602
+ "jobId": "uuid",
603
+ "batchId": "uuid",
604
+ "slaveId": "uuid",
605
+ "operation": "tool.run",
606
+ "args": {},
607
+ "requestedByChatId": "123",
608
+ "issuedAt": "ISO-8601",
609
+ "expiresAt": "ISO-8601",
610
+ "scope": "declared-capability"
611
+ }
612
+ ```
613
+
614
+ `batchId` identifica la ejecución grupal que originó el job. En una ejecución
615
+ individual también se crea un batch de un solo miembro para mantener un único
616
+ modelo de streaming, cancelación y resultados.
617
+
618
+ Slave persiste el estado `accepted` antes de producir un efecto externo. La
619
+ repetición de un `jobId` devuelve el estado o resultado almacenado y nunca vuelve
620
+ a ejecutar la operación. La salida usa chunks ordenados y límites de bytes. La
621
+ cancelación detiene el árbol de procesos del que Slave es dueña. Los estados
622
+ finales son `completed`, `failed`, `cancelled` y `expired`.
623
+
624
+ La recomendación inicial es fallar de forma clara cuando una Slave está offline.
625
+ La programación durable de trabajos offline puede
626
+ agregarse más adelante como una función explícita de Master con vencimiento y
627
+ cancelación visibles.
628
+
629
+ ## Autorización
630
+
631
+ El acceso a una Slave se concede al chat que pidió a Master crear la URL de
632
+ conexión. Los demás chats autorizados requieren una concesión explícita.
633
+ Incluir un `chatId` arbitrario en una solicitud remota no constituye
634
+ autorización.
635
+
636
+ Pertenecer a un grupo no concede acceso. Al resolver un selector, Master detecta
637
+ cualquier Slave que el chat no pueda administrar y falla el preflight completo;
638
+ nunca la elimina silenciosamente ni usa el grupo para eludir una concesión
639
+ individual.
640
+
641
+ Slave aplica la intersección de:
642
+
643
+ 1. conexión TCP iniciada por Slave hacia el endpoint configurado de Master;
644
+ 2. identidad criptográfica de Master emparejada;
645
+ 3. identidad del chat autorizado;
646
+ 4. capacidad publicada por Slave;
647
+ 5. política de privilegios de Slave;
648
+ 6. raíces permitidas y requisitos de herramientas;
649
+ 7. validez y vencimiento del job.
650
+
651
+ El modelo de Master no puede ampliar estos permisos mediante prompting.
652
+
653
+ ## Estado y ciclo de vida del servicio
654
+
655
+ Todas las rutas se obtienen mediante los helpers públicos de runtime. La tool no
656
+ construye raíces propias. Agrupaciones de estado propuestas:
657
+
658
+ ```text
659
+ ~/.arisa/state/tools/master-slave/ # daemon, identidad, pares y journal global
660
+ ~/.arisa/chats/<chatId>/state/tools/master-slave/ # grupos, concesiones y batches del chat
661
+ ~/.arisa/tools/master-slave/ # paquete instalado, sin estado mutable
662
+ ~/.arisa/tools/ # demás herramientas locales
663
+ ```
664
+
665
+ Las claves privadas y el material persistido de sesión usan permisos de archivo
666
+ restrictivos y escrituras atómicas. Master conserva cada secreto de conexión
667
+ sólo hasta su consumo o vencimiento. Slave no persiste la URL completa:
668
+ después de validarla conserva únicamente el endpoint de Master y las identidades
669
+ emparejadas. Estos valores no se guardan en el JSON normal de configuración del
670
+ runtime.
671
+
672
+ Superficie propuesta de la CLI:
673
+
674
+ ```text
675
+ arisa slave <tcp://ip_master:port/secret>
676
+ arisa slave start
677
+ arisa slave stop
678
+ arisa slave restart
679
+ arisa slave status
680
+ arisa slave log
681
+ arisa slave unpair
682
+ arisa slave tools
683
+ ```
684
+
685
+ Ejecutar `arisa` normalmente sigue significando Arisa Master. Los comandos de
686
+ servicio local de Slave no deben activar el bootstrap de Telegram ni la
687
+ validación de Pi. `arisa slave <url>` es el único comando de enlace: en una
688
+ instalación nueva realiza el bootstrap y, si encuentra una identidad local ya
689
+ emparejada, conserva esa identidad y actualiza el endpoint únicamente después de
690
+ autenticar a la misma Master. No se agregan flags ni preguntas para transportar
691
+ datos que ambos lados pueden intercambiar durante el handshake.
692
+
693
+ La URL se recibe como un único argumento posicional. Slave valida su formato,
694
+ extrae el endpoint y usa el secreto sólo para esa conexión. No imprime ni escribe
695
+ el argumento completo en sus logs. El usuario debe tratar el comando como una
696
+ credencial temporal porque su shell podría conservarlo en el historial.
697
+
698
+ ## Fases de implementación
699
+
700
+ ### 1. IPC general de daemons
701
+
702
+ - Extender el runtime compartido con socket Unix o named pipe, frames
703
+ multiplexados, streaming y backpressure.
704
+ - Extender ToolRegistry para parsear NDJSON incremental, emitir eventos de
705
+ ejecución y conservar compatibilidad con la respuesta JSON única actual.
706
+ - Conservar el journal durable, reclamo atómico, idempotencia y recuperación al
707
+ reiniciar sin polling durante la operación normal.
708
+ - Migrar las tools daemon existentes y verificar que no cambie su contrato CLI,
709
+ salud, supervisión ni recuperación.
710
+
711
+ ### 2. Tool daemon y modo Slave headless
712
+
713
+ - Crear la tool oficial global `master-slave` con roles Master y Slave.
714
+ - Hacer que `arisa slave <url>` instale y verifique la tool, inicie el host
715
+ headless y le entregue el secreto sin persistirlo en metadata de arranque.
716
+ - Reutilizar el IPC y ToolRegistry públicos para descubrir y ejecutar
717
+ capacidades locales, sin importar internals de Core.
718
+ - Verificar una instalación global limpia y el contenido publicado de Core y de
719
+ la tool.
720
+
721
+ ### 3. Servidor TCP y emparejamiento seguro
722
+
723
+ - Implementar dentro de la tool el listener TCP de Master y el cliente saliente
724
+ de Slave por separado de la ejecución de jobs.
725
+ - Implementar parsing estricto del único argumento
726
+ `tcp://ip_master:port/secret`, framing, consumo del secreto, handshake de
727
+ identidad, cifrado, reconexión, revocación y versionado del protocolo.
728
+ - Verificar que la dirección remota normalizada del socket saliente sea la IP
729
+ literal incluida en la URL antes de continuar el handshake.
730
+ - Consumir en Master secretos de conexión de uso único y vida corta.
731
+ - Si Slave ya tiene un emparejamiento, exigir la misma identidad de Master antes
732
+ de guardar un endpoint diferente o aceptar jobs.
733
+ - Permitir únicamente heartbeat e intercambio de perfil.
734
+
735
+ ### 4. Integración con Master y grupos
736
+
737
+ - Exponer desde la tool el registro de Slaves y las operaciones de
738
+ descubrimiento.
739
+ - Asociar las Slaves con el chat solicitante.
740
+ - Implementar grupos muchos-a-muchos, selector común, snapshot de membresía,
741
+ deduplicación y preflight de autorización y capacidades.
742
+ - Implementar batches con concurrencia configurada, streaming etiquetado,
743
+ cancelación y resultados por Slave.
744
+ - Agregar eventos de conexión, desconexión, reconexión y revocación.
745
+ - Detectar una desconexión que supere el umbral configurado, deduplicar el aviso
746
+ y recomendar revisar el servicio y la red.
747
+ - Explicar que Master no puede distinguir por sí sola entre una Slave apagada,
748
+ un problema de red y un endpoint incorrecto.
749
+ - Permitir que Pi vea dinámicamente los catálogos de herramientas remotas.
750
+
751
+ ### 5. Operaciones de sólo lectura
752
+
753
+ - Implementar inspección, listado, lectura y listado de herramientas.
754
+ - Verificar contención en las raíces, path traversal, escape mediante symlinks,
755
+ límites de frames, límites de salida y aislamiento por chat.
756
+
757
+ ### 6. Ejecución de herramientas y comandos
758
+
759
+ - Implementar `run_slave_tool` y transferencia cifrada de artifacts.
760
+ - Preservar configuración y estado de herramientas por chat.
761
+ - Agregar política de instalación, verificación de fuentes firmadas y controles
762
+ de permisos.
763
+ - Implementar creación directa de procesos, streaming de salida, timeouts,
764
+ cancelación, identidad durable de jobs y recuperación después de reinicios.
765
+ - Probar los modos usuario dedicado, root restringido y root completo en hosts
766
+ descartables.
767
+
768
+ ### 7. Operación
769
+
770
+ - Extender Arisa Doctor con la salud de la tool daemon y su IPC, listener de
771
+ Master, endpoint configurado, conexión saliente de Slave, identidad, versión,
772
+ jobs, herramientas, estado offline y secretos de conexión pendientes.
773
+ - Ejercitar desconexiones reales, reconexiones, revocación, actualizaciones y
774
+ dos máquinas alcanzables mediante IP pública, red privada o VPN.
775
+
776
+ ## Regresiones requeridas
777
+
778
+ - Un job de daemon se persiste antes de notificarse y empieza sin esperar el
779
+ intervalo histórico de polling.
780
+ - Reiniciar el daemon recupera jobs `queued` o `accepted`; repetir una
781
+ notificación IPC no repite sus efectos.
782
+ - Los chunks multiplexados conservan secuencia por job y respetan backpressure
783
+ sin crecimiento ilimitado de memoria.
784
+ - Perder el consumidor del stream no elimina el resultado durable del job.
785
+ - Una tool existente que devuelve un único JSON sigue funcionando sin cambios.
786
+ - El parser NDJSON acepta líneas fragmentadas, rechaza secuencias inválidas y
787
+ entrega exactamente un resultado terminal por job.
788
+ - ToolRegistry no acumula en memoria la salida completa de un job con streaming
789
+ y mantiene `stderr` fuera del canal de eventos.
790
+ - Instalar o actualizar `master-slave` exige verificar su origen e integridad;
791
+ el secreto de conexión no aparece en logs ni metadata del daemon.
792
+ - Un secreto inicial no puede consumirse dos veces ni después de vencer o ser
793
+ rotado.
794
+ - Una conexión que no conoce el secreto inicial no puede completar el primer
795
+ handshake.
796
+ - El parser rechaza esquemas distintos de `tcp`, hostnames, puertos implícitos,
797
+ secretos vacíos, segmentos adicionales, usuario, query y fragmento.
798
+ - Slave sólo intenta el endpoint literal incluido en la URL y comprueba que la
799
+ dirección remota normalizada del socket coincide antes del handshake.
800
+ - El listener de Master procesa exclusivamente frames válidos del protocolo
801
+ autenticado de Arisa.
802
+ - Un secreto de conexión sólo sirve para el chat que lo solicitó.
803
+ - Una Slave emparejada no reemplaza el endpoint ni acepta jobs desde él sin
804
+ autenticar la misma identidad de Master.
805
+ - Los avisos de desconexión se emiten al superar el umbral configurado, se
806
+ deduplican y no afirman conocer la causa.
807
+ - IPv4 y su representación IPv4-mapped IPv6 comparan como la misma dirección;
808
+ direcciones diferentes no se aceptan por coincidencias textuales parciales.
809
+ - Slave no abre un puerto de entrada para este protocolo.
810
+ - La URL secreta no se registra ni se persiste después de ser procesada.
811
+ - Los fingerprints de pares incorrectos y los transcripts modificados impiden
812
+ el emparejamiento.
813
+ - Los frames repetidos, reordenados, demasiado grandes o corruptos cierran la
814
+ sesión.
815
+ - Un chat sin concesión no puede inspeccionar ni operar una Slave.
816
+ - Resolver varias Slaves y grupos produce una unión deduplicada y un snapshot
817
+ inmutable para el batch.
818
+ - La pertenencia a un grupo no amplía permisos; una Slave no autorizada hace
819
+ fallar el preflight antes de ejecutar efectos.
820
+ - Si falta una capacidad o una Slave requerida está desconectada, el modo seguro
821
+ no inicia ningún job del batch; la ejecución parcial sólo se habilita por
822
+ pedido explícito.
823
+ - Cada chunk y resultado grupal identifica la Slave correcta aunque varios jobs
824
+ se ejecuten de forma concurrente.
825
+ - Eliminar un grupo no elimina Slaves ni modifica batches ya aceptados.
826
+ - Los archivos no pueden salir de las raíces configuradas mediante traversal o
827
+ symlinks.
828
+ - Una herramienta faltante en Slave produce `capability_missing` sin
829
+ comportamientos de fallback.
830
+ - Los valores de configuración de herramientas y las variables de entorno nunca
831
+ se publican.
832
+ - Un job ID repetido no puede repetir un efecto externo.
833
+ - Un reinicio durante la ejecución deja un estado durable y accionable del job.
834
+ - La revocación desconecta a Slave e impide su reconexión.
835
+ - El modo root sólo puede seleccionarse explícitamente y permanece visible en
836
+ los diagnósticos.
837
+
838
+ ## Decisiones fuera de v1
839
+
840
+ - Otras plataformas de servicio además de Linux con systemd.
841
+ - Hostnames como endpoint de Master; v1 exige una IP literal.
842
+ - Programación durable de jobs mientras una Slave está offline.
843
+ - Instalación remota silenciosa de tools, incluso cuando sean oficiales y de
844
+ bajo impacto.