OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-identity commits / ecddf61

identity-apply: complete view-migration wiring (link-userId + cascade-delete)
Two pre-existing failures shared one root cause: the view-migration made the autobase view authoritative for identity records + reverse indexes (getIdentity / getIdentityByPublicKey read the view first), but two write paths still only touched the legacy db, so the view fell out of sync: - LINK_IDENTITY apply now stamps userId onto the view's identity:<id> record (was db-only → getIdentity returned a record with no userId). - DELETE_USER apply now removes the view's pubkey/sshkey/noisekey/username/ identity reverse indexes for the deleted user's identities (deleteUserCascade cleaned only the db → view indexes survived a delete). Both are additive, idempotent, and replicate to peers; they heal existing records on reopen. Fixes identity-registry.test.js "identity record has userId" and "pubkey index gone" (suite now 30/30, 92/92 under node). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ecddf6122ec78381b8a840187d5ffd7bcc6d65f6
by plankatron <fleeky@gmail.com> · 2026-06-04T14:53:54.000Z

Changed files (1) +34 -2

lib/auth/identity-apply.js +34 -2
……@@ -138,10 +138,29 @@ export function createApplyFunction (opts = {}) {
138138 const existing = await b.get(key)
139139 if (!existing) continue
140140
141 // Remove identity index entries
142 for (const id of (existing.value.identities || [])) {
141 const user = existing.value
142 // Remove identity index entries AND the identity records + their
143 // reverse indexes FROM THE VIEW. Since the view-migration these
144 // records live in the view; deleteUserCascade only cleaned the
145 // legacy db, so the view's pubkey/sshkey/noisekey indexes
146 // survived a delete. Mirror REGISTER_IDENTITY's index writes in
147 // reverse so a cascade is complete and replicates to peers.
148 for (const id of (user.identities || [])) {
143149 await b.del(`${prefix}user-identity:${id}`)
150 const idEntry = await b.get(`${prefix}identity:${id}`)
151 if (idEntry) {
152 const idRec = idEntry.value
153 if (idRec.publicKey) await b.del(`${prefix}pubkey:${idRec.publicKey}`)
154 if (idRec.method === 'ssh-key' && idRec.credentials && idRec.credentials.fingerprint) {
155 await b.del(`${prefix}sshkey:${idRec.credentials.fingerprint}`)
156 }
157 if (idRec.method === 'noise-key' && idRec.credentials && idRec.credentials.publicKey) {
158 await b.del(`${prefix}noisekey:${idRec.credentials.publicKey}`)
159 }
160 await b.del(`${prefix}identity:${id}`)
161 }
144162 }
163 if (user.username) await b.del(`${prefix}username:${user.username}`)
145164 await b.del(key)
146165 break
147166 }
……@@ -186,6 +205,19 @@ export function createApplyFunction (opts = {}) {
186205 await b.put(key, user)
187206 }
188207 }
208 // Stamp the owning userId onto the identity record IN THE VIEW.
209 // Since the view-migration, identity records live in the view and
210 // getIdentity reads it first; the registry method only wrote
211 // userId to the legacy db, leaving the view record without it
212 // (cross-daemon link ownership invisible). Idempotent.
213 const idKey = `${prefix}identity:${op.identityId}`
214 const idEntry = await b.get(idKey)
215 if (idEntry && idEntry.value.userId !== op.userId) {
216 const idRec = idEntry.value
217 idRec.userId = op.userId
218 idRec.updatedAt = Date.now()
219 await b.put(idKey, idRec)
220 }
189221 break
190222 }
191223