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.
- package/AGENTS.md +12 -4
- package/ARISA-MASTER-SLAVE-SPEC.md +844 -0
- package/README.md +25 -0
- package/package.json +1 -1
- package/src/core/agent/agent-manager.js +55 -12
- package/src/core/config/config-defaults.js +3 -1
- package/src/core/tools/daemon-processes.js +48 -6
- package/src/core/tools/daemon-runtime.js +370 -138
- package/src/core/tools/ipc-client.js +3 -0
- package/src/core/tools/official-tool-catalog.js +32 -0
- package/src/core/tools/official-tool-installer.js +183 -0
- package/src/core/tools/tool-registry.js +203 -18
- package/src/core/tools/tool-resource-note-store.js +78 -0
- package/src/index.js +25 -2
- package/src/official-tools.lock.json +40 -0
- package/src/runtime/arisa-capabilities.js +43 -3
- package/src/runtime/create-app.js +9 -0
- package/src/runtime/create-headless-app.js +77 -0
- package/src/runtime/doctor.js +27 -2
- package/src/runtime/headless-tool-executor.js +45 -0
- package/src/runtime/paths.js +16 -4
- package/src/runtime/secure-request-file.js +21 -0
- package/src/runtime/slave-bootstrap-url.js +51 -0
- package/src/runtime/slave-cli.js +267 -0
- package/src/runtime/slave-service.js +225 -0
- package/src/runtime/tool-usage-report.js +11 -3
- package/src/transport/telegram/bot.js +37 -7
- package/test/capabilities-security.test.js +29 -0
- package/test/daemon-catalog-conformance.test.js +3 -1
- package/test/daemon-runtime.test.js +58 -2
- package/test/official-tool-installer.test.js +107 -0
- package/test/paths.test.js +6 -12
- package/test/slave-cli.test.js +282 -0
- package/test/telegram-text-artifact.test.js +24 -1
- package/test/tool-capability-search.test.js +55 -0
- package/test/tool-registry-run.test.js +70 -1
- package/test/tool-resource-note.test.js +50 -0
- package/test/tool-usage.test.js +10 -5
- 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.
|