@dotrino/vaultd 0.153.2 → 0.154.1

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/lib/src/index.js CHANGED
@@ -217,7 +217,10 @@ export async function startDeviceVault (identity, { proxyUrl, client: injectedCl
217
217
  deviceId: x.sub ? await deviceIdOf(x.sub) : null, sub: x.sub || null,
218
218
  label: x.label || '', scope: x.scope, seq: x.seq, nonce: x.nonce
219
219
  })))
220
- send(from, { type: MSG.DEVICES_RESULT, devices, revoked: (revoked || []).map((r) => r.nonce || r) })
220
+ // La cadena de selladores va con la lista (como en el daemon): es lo que un aparato de
221
+ // Node necesita para firmar a nombre de la cuenta de forma que un extraño lo compruebe.
222
+ const sealerChain = await identity.sealerChain?.().catch(() => null) || null
223
+ send(from, { type: MSG.DEVICES_RESULT, devices, revoked: (revoked || []).map((r) => r.nonce || r), sealerChain })
221
224
  // ¿el que consulta es una máquina revocada que reapareció? → re-emite el REVOKED firmado.
222
225
  // Se busca en `revokedCerts`: desde que `issued` solo trae lo vigente, los retirados
223
226
  // ya no están ahí y este aviso dejaba de dispararse (se caía en silencio).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.153.2",
3
+ "version": "0.154.1",
4
4
  "type": "module",
5
5
  "description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "@dotrino/identity": "^0.111.0",
26
26
  "@dotrino/opaque": "0.1.0",
27
27
  "@dotrino/passmanager": "0.17.0",
28
- "@dotrino/proxy-client": "^0.32.0",
28
+ "@dotrino/proxy-client": "^0.32.2",
29
29
  "@dotrino/roadmap": "0.3.19",
30
30
  "@dotrino/store": "0.11.0",
31
31
  "@dotrino/update": "0.5.1",
package/src/transport.js CHANGED
@@ -36,7 +36,7 @@ export async function masterPubkeyOf (identity) {
36
36
  * @param {string} [opts.url] URL del proxy (default wss://proxy.dotrino.com).
37
37
  * @returns {Promise<{ client, token:string, identify():Promise<void> }>}
38
38
  */
39
- export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, commKey = null, log = () => {}, makeClient = null, announce = null, recheckMs = 60_000, reannounceMs = 10 * 60_000 }) {
39
+ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, commKey = null, log = () => {}, makeClient = null, announce = null, recheckMs = 60_000, patienceMs = 5 * 60_000, reannounceMs = 10 * 60_000 }) {
40
40
  installNodeGlobals(dir)
41
41
  // Import dinámico DESPUÉS de instalar los globals que el paquete usa.
42
42
  // `WebSocketProxyClient` (la clase) y NO el helper `getWebSocketProxyClient`:
@@ -222,21 +222,33 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
222
222
  * nada.
223
223
  */
224
224
  const RECHECK_MS = recheckMs
225
- const PATIENCE_MS = 5 * 60_000
225
+ const PATIENCE_MS = patienceMs
226
226
  const REANNOUNCE_MS = reannounceMs // la mitad de lo que el proxio guarda una entrada
227
227
  let downSince = 0
228
228
  let saidDown = false
229
229
  client.on('disconnect', ({ code, reason } = {}) => {
230
+ // Sin socket no hay nadie atado al proxio: lo que se identificó era la conexión anterior.
231
+ identificado = false
230
232
  if (!downSince) downSince = Date.now()
231
233
  if (saidDown) return
232
234
  saidDown = true
233
235
  log(`[vault] off the proxy (${code || '?'}${reason ? ': ' + reason : ''}) — nobody can reach this vault while this lasts`)
234
236
  })
237
+ // LOS INTENTOS SE VEN. El 2026-10-15 la bóveda pasó hora y media fuera y no quedó ni una
238
+ // línea de si el transporte estaba reintentando o no; en el proxio no llegó ninguno. El
239
+ // primero se dice, y luego uno de cada 15 (un minuto con el retardo de 4 s).
240
+ client.on('reconnecting', (attempt) => {
241
+ if (attempt === 1 || attempt % 15 === 0) log(`[vault] reconnecting to the proxy (attempt ${attempt})`)
242
+ })
235
243
  client.on('reconnect_failed', (attempts) => {
236
244
  log(`[vault] the transport gave up reconnecting after ${attempts} attempt(s) — this vault is unreachable until it is restarted`)
237
245
  })
238
246
  const watchdog = setInterval(async () => {
239
- if (identificado && client.token) {
247
+ // CONECTADO DE VERDAD, no «tengo un token». El token es el de la última conexión y no se
248
+ // borra al caerse, así que mirarlo daba la bóveda por vuelta con el socket cerrado: el
249
+ // 2026-10-15 dijo «back on the proxy after 50s», no volvió a marcar nunca y pasó hora y
250
+ // media fuera, avisando cada 10 minutos de que no podía anunciarse.
251
+ if (identificado && client.isConnected && client.token) {
240
252
  if (downSince) log(`[vault] back on the proxy after ${Math.round((Date.now() - downSince) / 1000)}s`)
241
253
  downSince = 0; saidDown = false
242
254
  // EL ANUNCIO CADUCA: el proxio borra una entrada de canal a los 20 minutos de publicada.
package/src/vault.js CHANGED
@@ -1288,6 +1288,12 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1288
1288
  // sigue pidiendo `lee`. Antes no preguntaba nada, así que un aparato al que le quitaste
1289
1289
  // el permiso seguía viéndolo todo hasta que su papel caducara.
1290
1290
  const record = (await identity.profileActa?.().catch(() => null))?.acta || null
1291
+ // LA CADENA DE SELLADORES VIAJA CON LA LISTA, también para quien solo puede ver
1292
+ // revocaciones. Es lo que un aparato de Node necesita para FIRMAR a nombre de la cuenta
1293
+ // de forma que un extraño lo compruebe (`verifySignedFor` exige la cadena desde el
1294
+ // génesis), y hasta ahora ningún agente headless podía: solo el navegador la tenía. Es
1295
+ // corta a propósito (génesis, cambios de sellador, actual), así que no pasa por `fitChain`.
1296
+ const sealerChainOf = async () => { try { return (await identity.sealerChain?.()) || null } catch (e) { log('[vault] could not build the sealer chain:', e.message); return null } }
1291
1297
  const puedeVerTodo = !!record && Acta.memberCanScope(record, chk.device, SCOPE.READ)
1292
1298
  const { issued, revoked } = await identity.listDelegations()
1293
1299
  if (!puedeVerTodo) {
@@ -1296,7 +1302,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1296
1302
  return reply(from, { type: MSG.ERROR, error: 'unauthorized: this vault has no record to decide with' })
1297
1303
  }
1298
1304
  audit('devices', { device: await deviceIdOf(chk.device).catch(() => null), solo: 'revocaciones' })
1299
- return reply(from, { type: MSG.DEVICES_RESULT, devices: [], revoked, blocked: blocked.pubs(), acta: record })
1305
+ return reply(from, { type: MSG.DEVICES_RESULT, devices: [], revoked, blocked: blocked.pubs(), acta: record, sealerChain: await sealerChainOf() })
1300
1306
  }
1301
1307
  // El acta viaja con la lista: así cada dispositivo se entera de los cambios de
1302
1308
  // política (quién manda, quién puede qué) sin un canal aparte.
@@ -1329,7 +1335,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1329
1335
  // LA SUBACTA VIAJA CON EL ACTA. Es la mitad de «se comparte entre todos los
1330
1336
  // dispositivos»: sin esto, una renuncia sin sellar solo la respeta esta máquina.
1331
1337
  // Cada entrada va firmada por su sujeto, así que quien la recibe la verifica sola.
1332
- reply(from, fitChain({ type: MSG.DEVICES_RESULT, devices, revoked, blocked: blocked.pubs(), acta: record, chain, vault: MI_VERSION, subacta: subacta.entries() }))
1338
+ reply(from, fitChain({ type: MSG.DEVICES_RESULT, devices, revoked, blocked: blocked.pubs(), acta: record, chain, sealerChain: await sealerChainOf(), vault: MI_VERSION, subacta: subacta.entries() }))
1333
1339
  }
1334
1340
 
1335
1341
  /**