@dotrino/vaultd 0.150.0 → 0.151.0

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.
@@ -522,8 +522,9 @@ export async function registerEncKey ({ dir, ns, proxyUrl, masterPubkey, device,
522
522
  * privadas al pasar.
523
523
  *
524
524
  * Lo que decide la bóveda, no este cliente:
525
- * · si hay quien apruebe, se pide aprobación siempre, y al aprobarse puede EDITAR;
526
- * · si nadie puede aprobar, solo AÑADE lo que falta (una que ya exista falla con `exists`);
525
+ * · AÑADIR una variable nueva es directo: se guarda en el acto;
526
+ * · EDITAR una que ya existe pide aprobación (la tanda entera queda en espera);
527
+ * · si nadie puede aprobar, una que ya exista falla con `exists`;
527
528
  * · BORRAR no existe por aquí.
528
529
  *
529
530
  * @param {object} o
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.150.0",
3
+ "version": "0.151.0",
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": {
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 }) {
39
+ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, commKey = null, log = () => {}, makeClient = null, announce = null, recheckMs = 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`:
@@ -174,8 +174,10 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
174
174
  * existe». Si el anuncio falla, la bóveda sigue sirviendo a todo lo demás y se dice en voz
175
175
  * alta: lo que se pierde es que la encuentren por su dirección, no que deje de funcionar.
176
176
  */
177
+ let announcedAt = 0
177
178
  const announceAccount = async () => {
178
179
  if (!announce || !identificado) return
180
+ announcedAt = Date.now()
179
181
  try { await client.publish(announce) } catch (e) {
180
182
  log(`[vault] could not announce this account on the proxy (${e.message}) — a borrowed machine will not find this vault by its address`)
181
183
  }
@@ -219,8 +221,9 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
219
221
  * `connect()` es seguro de repetir: si ya está conectado devuelve el token y no abre
220
222
  * nada.
221
223
  */
222
- const RECHECK_MS = 60_000
224
+ const RECHECK_MS = recheckMs
223
225
  const PATIENCE_MS = 5 * 60_000
226
+ const REANNOUNCE_MS = reannounceMs // la mitad de lo que el proxio guarda una entrada
224
227
  let downSince = 0
225
228
  let saidDown = false
226
229
  client.on('disconnect', ({ code, reason } = {}) => {
@@ -236,6 +239,11 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
236
239
  if (identificado && client.token) {
237
240
  if (downSince) log(`[vault] back on the proxy after ${Math.round((Date.now() - downSince) / 1000)}s`)
238
241
  downSince = 0; saidDown = false
242
+ // EL ANUNCIO CADUCA: el proxio borra una entrada de canal a los 20 minutos de publicada.
243
+ // Sin renovarlo, entrar con usuario y contraseña solo funcionaba los primeros 20 minutos
244
+ // tras identificarse, y desde fuera se veía como «no hay ninguna bóveda en esa dirección»
245
+ // con la bóveda encendida (2026-10-12, la demo de los revisores).
246
+ if (announce && Date.now() - announcedAt >= REANNOUNCE_MS) await announceAccount()
239
247
  return
240
248
  }
241
249
  if (!downSince) downSince = Date.now()
package/src/vault.js CHANGED
@@ -1477,9 +1477,9 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1477
1477
  * firma del autor, las envolturas, la espera de aprobación. Aquí no hay otra puerta.
1478
1478
  *
1479
1479
  * Los límites (dueño, 2026-09-29):
1480
- * · si hay alguien que aprueba, SIEMPRE se pide aprobación —`unattended` no exime— y con
1481
- * el sí puede también EDITAR lo que ya existe;
1482
- * · si nadie puede aprobar, no se pide y solo puede AÑADIR lo que falta;
1480
+ * · AÑADIR una variable nueva es directo, haya o no quien apruebe (dueño, 2026-10-12);
1481
+ * · EDITAR una que ya existe pide aprobación —`unattended` no exime—;
1482
+ * · si nadie puede aprobar, no se pide y lo que ya existe no se toca (`exists`);
1483
1483
  * · BORRAR, nunca: eso es de la consola y del CLI de la bóveda.
1484
1484
  *
1485
1485
  * Dos operaciones: `store.recipients` (para quién se sella: solo públicas, nada que ocultar)
@@ -1696,7 +1696,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1696
1696
  log(`[vault] ${ns}: ${deviceId || '????-????'} wants to store ${items.length} variable(s) — waiting for approval (${result.pending})`)
1697
1697
  } else {
1698
1698
  audit('store', { device: deviceId, ns, keys: result?.keys || [] })
1699
- log(`[vault] ${ns}: ${deviceId || '????-????'} stored ${(result?.keys || []).length} variable(s) (nobody can approve: add only)`)
1699
+ log(`[vault] ${ns}: ${deviceId || '????-????'} stored ${(result?.keys || []).length} new variable(s)`)
1700
1700
  await notifyMembers('vars', { by: deviceId, keys: result?.keys || [], ns })
1701
1701
  }
1702
1702
  return await answer({ op: 'store.result', ns, ts: Date.now(), keys: result?.keys || [], pending: result?.pending || null })
@@ -2811,13 +2811,18 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
2811
2811
  const owner = ns ? `ns:${ns}` : `dev:${pub}`
2812
2812
  const keys = []
2813
2813
  // ¿SE PIDE APROBACIÓN? Desde la mesa del admin, lo de siempre (`needsApproval`: quien
2814
- // tiene `unattended` escribe solo). Desde el propio SERVICIO que guarda en su cajón, se
2815
- // pide SIEMPRE que haya quien apruebe, tenga o no `unattended` (dueño, 2026-09-29:
2816
- // «el unattended también pediría aprobación»): `unattended` es para llevarse claves al
2817
- // arrancar, no para reescribir su propia configuración. Sin nadie que apruebe no se
2818
- // pide —la regla de siempre— y entonces solo puede añadir.
2814
+ // tiene `unattended` escribe solo). Desde el propio SERVICIO que guarda en su cajón, la
2815
+ // aprobación es SOLO PARA EDITAR (dueño, 2026-10-12: «la aprobación solamente es para
2816
+ // editar una env; para almacenarla es directo»): una variable nueva no le quita nada a
2817
+ // nadie y se guarda en el acto; reemplazar una que ya existe pide el sí de quien
2818
+ // aprueba, tenga o no `unattended` el servicio (dueño, 2026-09-29: «el unattended
2819
+ // también pediría aprobación»). Una tanda con alguna que ya existe se pide ENTERA: va
2820
+ // todo o nada. Sin nadie que apruebe no se pide —la regla de siempre— y lo que ya
2821
+ // existe falla con `exists`.
2819
2822
  const record = await refreshActa()
2820
- const ask = fromService ? hasApprover(record) : needsApproval(caller, record)
2823
+ const ask = fromService
2824
+ ? hasApprover(record) && items.some((it) => it?.key && secrets.has(owner, it.key))
2825
+ : needsApproval(caller, record)
2821
2826
  for (const it of items) {
2822
2827
  if (!it?.key || !it?.sealed) throw new Error('var.setMany: each variable needs its key and its sealed envelope')
2823
2828
  await checkAuthor(owner, it.key, it.sealed, caller, { allowOverwrite: ask })