@dotrino/identity 0.112.0 → 0.113.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.112.0",
3
+ "version": "0.113.0",
4
4
  "description": "Identidad y rating de usuarios compartidos entre apps de Dotrino (vault iframe + postMessage)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/vault/acta.js CHANGED
@@ -328,8 +328,9 @@ export async function verifySealerLink (link) {
328
328
  *
329
329
  * Es la hermana de `verifySealerChain`, que hace lo mismo con actas enteras para quien las
330
330
  * tiene. Lo que comprueba, y por qué basta:
331
- * 1. el primero es el GÉNESIS: `seq 1`, sin `prev`, y firmado por `profileId` — el ancla,
332
- * que no se puede fabricar sin la llave que da nombre al perfil;
331
+ * 1. el primero está firmado por `profileId` — el ancla, que no se puede fabricar sin la
332
+ * llave que da nombre al perfil. Normalmente es el génesis (`seq 1`, sin `prev`), pero
333
+ * no tiene por qué: una cuenta que perdió su génesis ancla en un eslabón posterior;
333
334
  * 2. cada siguiente lo firmó alguien a quien el ANTERIOR autorizaba;
334
335
  * 3. y apunta al anterior por `seq` + hash, así que no se cuela uno de otra rama.
335
336
  *
@@ -339,7 +340,9 @@ export async function verifySealerLink (link) {
339
340
  export async function verifySealerLinkChain (chain, { expectedProfileId = null } = {}) {
340
341
  if (!Array.isArray(chain) || !chain.length) return { ok: false, reason: 'cadena-vacia' }
341
342
  const [raiz] = chain
342
- if (raiz?.seq !== 1 || raiz?.prev != null) return { ok: false, reason: 'no-empieza-en-genesis' }
343
+ // La misma regla que en `verifySealerChain`: la raíz la ancla la llave del perfil, no el
344
+ // número 1 (una cuenta puede haber perdido su génesis; ver allí).
345
+ if (!Number.isInteger(raiz?.seq) || raiz.seq < 1) return { ok: false, reason: 'no-empieza-en-genesis' }
343
346
  if (raiz.by !== raiz.profileId) return { ok: false, reason: 'genesis-no-autofirmado' }
344
347
  if (expectedProfileId != null && raiz.profileId !== expectedProfileId) return { ok: false, reason: 'otro-perfil' }
345
348
  const vr = await verifySealerLink(raiz)
@@ -637,8 +640,15 @@ export async function verifySealerChain (chain, { expectedProfileId = null } = {
637
640
  if (!Array.isArray(chain) || !chain.length) return { ok: false, reason: 'cadena-vacia' }
638
641
  const [raiz] = chain
639
642
 
640
- if (raiz.seq !== 1 || raiz.sealerAnchor != null) return { ok: false, reason: 'no-empieza-en-genesis' }
641
- // El ancla: el génesis se firma a sí mismo con la llave que da nombre al perfil.
643
+ // EL ANCLA ES LA LLAVE DEL PERFIL, NO EL NÚMERO 1 (dueño, 2026-10-14). La raíz vale si
644
+ // está firmada por la llave que da nombre al perfil, sea el génesis o un acta posterior.
645
+ // Antes se exigía `seq 1`, y eso dejaba sin cadena a toda cuenta anterior a las actas v5:
646
+ // su génesis no llevaba `sealerChanged`, la ventana de retención lo podó, y no hay de
647
+ // dónde recuperarlo. Exigir el génesis no protegía de nada: quien tenga la llave del
648
+ // perfil fabrica un génesis alterno igual que un acta posterior, y contra eso la capa es
649
+ // el registro público. Lo que sí sostiene la cadena —que cada eslabón lo firmó alguien a
650
+ // quien el anterior autorizaba— sigue intacto.
651
+ if (!Number.isInteger(raiz.seq) || raiz.seq < 1) return { ok: false, reason: 'no-empieza-en-genesis' }
642
652
  if (raiz.sealedBy !== raiz.profileId) return { ok: false, reason: 'genesis-no-autofirmado' }
643
653
  if (expectedProfileId != null && raiz.profileId !== expectedProfileId) return { ok: false, reason: 'otro-perfil' }
644
654
  const vr = await verifyActa({ acta: raiz })
package/vault/core.js CHANGED
@@ -873,7 +873,9 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
873
873
  h.sort((a, b) => a.seq - b.seq)
874
874
  const ventana = h.slice(-ACTA_WINDOW)
875
875
  const enVentana = new Set(ventana.map((a) => a.seq))
876
- const eslabones = h.filter((a) => a.sealerChanged && !enVentana.has(a.seq))
876
+ // El génesis cuenta siempre como eslabón: las actas anteriores a v5 no llevaban la
877
+ // marca, y por eso la poda se llevó el génesis de las cuentas viejas.
878
+ const eslabones = h.filter((a) => (a.sealerChanged || a.seq === 1) && !enVentana.has(a.seq))
877
879
  kv.setItem(ACTA_HISTORY_STORAGE, JSON.stringify([...eslabones, ...ventana].sort((a, b) => a.seq - b.seq)))
878
880
  }
879
881
 
@@ -888,7 +890,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
888
890
  // La vigente cuenta como parte del material aunque todavía no esté en la historia
889
891
  // (ahí entra al dejar de serlo). Sin esto, una cuenta recién creada devolvía [].
890
892
  const porSeq = new Map([...loadHistory(), cur].map((a) => [a.seq, a]))
891
- const eslabones = [...porSeq.values()].filter((a) => a.sealerChanged).sort((a, b) => a.seq - b.seq)
893
+ const eslabones = [...porSeq.values()].filter((a) => a.sealerChanged || a.seq === 1).sort((a, b) => a.seq - b.seq)
892
894
  // Y la actual va al final si ella misma no es un eslabón: el verificador necesita ver
893
895
  // la cabeza para saber en qué `seq` está y quién la selló.
894
896
  return eslabones.length && eslabones[eslabones.length - 1].seq === cur.seq ? eslabones : [...eslabones, cur]
@@ -2506,6 +2508,10 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
2506
2508
  * Una cuenta de una sola bóveda devuelve SIEMPRE eso y nada más, y no puede quedarse
2507
2509
  * obsoleta: su conjunto de selladores no puede cambiar, porque el único no puede
2508
2510
  * quitarse a sí mismo y no hay otro que se lo quite.
2511
+ *
2512
+ * SIN GÉNESIS, la cadena es `[acta actual]` y vale igual mientras la actual esté firmada
2513
+ * por la llave del perfil: la raíz la ancla esa llave, no el número 1 (`verifySealerChain`).
2514
+ * Pasa en las cuentas anteriores a v5, cuyo génesis se podó sin la marca de eslabón.
2509
2515
  */
2510
2516
  async sealerChain () { return sealerChain() },
2511
2517