import { Weave , WeaveNode , WeaveResult , AtomAddedResult , AtomConflictResult , iterateCausalGroup , iterateChildren , first , Atom , AtomRemovedResult , iterateSiblings , iterateNewerSiblings , idEquals , } from '@casual-simulation/causal-trees/core2' ; import { BotOp , AuxOp , AuxOpType , ValueOp , DeleteOp , TagOp , CertificateOp , validateCertSignature , validateRevocation , RevocationOp , SignatureOp , validateSignedValue , tagValueHash , TagMaskOp , InsertOp , } from './AuxOpTypes' ; import { v5 as uuidv5 } from 'uuid' ; import { Bot , PartialBotsState , BotSpace } from '../bots/Bot' ; import { merge } from '../utils' ; import { hasValue , createBot } from '../bots/BotCalculations' ; import { merge as lodashMerge } from 'lodash' ; import { calculateOrderedEdits , findBotNode , findBotNodes , TextSegment , } from './AuxWeaveHelpers' ; import { apply , applyTagEdit , del , edit , insert , isTagEdit , mergeEdits , preserve , TagEditOp , } from './AuxStateHelpers' ; export const CERT_ID_NAMESPACE = 'a1307e2b-8d80-4945-9792-2cd483c45e24' ; export const CERTIFIED_SPACE = 'certified' ; param weave param result param state param space param initial export default function reducer ( weave , result , state = { } , space ? , initial = false ) { if ( result . type === 'atom_added' ) { return atomAddedReducer ( weave , result . atom , state , space , initial ) ; } else if ( result . type === 'atoms_added' ) { for ( let atom of result . atoms ) { atomAddedReducer ( weave , atom , state , space , initial ) ; } return state ; } else if ( result . type === 'conflict' ) { return conflictReducer ( weave , result , state , space , initial ) ; } else if ( result . type === 'atom_removed' ) { return atomRemovedReducer ( weave , result , state ) ; } return { } ; } function atomAddedReducer ( weave , atom , state , space , initial ) { const value = atom . value ; if ( value . type === AuxOpType . Bot ) { return botAtomAddedReducer ( atom , value , state , space ) ; } else if ( value . type === AuxOpType . Value ) { return valueAtomAddedReducer ( weave , atom , value , state , space ) ; } else if ( value . type === AuxOpType . Insert && ! initial ) { return insertAtomAddedReducer ( weave , atom , value , state , space ) ; } else if ( value . type === AuxOpType . Delete && ( ! initial || ! hasValue ( value . start ) ) ) { return deleteAtomAddedReducer ( weave , atom , value , state , space ) ; } else if ( value . type === AuxOpType . Certificate ) { return certificateAtomAddedReducer ( weave , < Atom < CertificateOp > > atom , value , state ) ; } else if ( value . type === AuxOpType . Revocation ) { return revokeAtomAddedReducer ( weave , atom , value , state ) ; } else if ( value . type === AuxOpType . Signature ) { return signatureAtomAddedReducer ( weave , atom , value , state ) ; } return { } ; } function atomRemovedReducer ( weave , result , state ) { let updates = removeAtom ( weave , result . ref . atom , result . ref , state ) ; for ( let sibling of iterateSiblings ( result . ref ) ) { removeAtom ( weave , sibling . atom , sibling , state ) ; } return updates ; } function botAtomAddedReducer ( atom , value , state , space ) { const id = value . id ; return addBot ( atom , id , state , space ) ; } function valueAtomAddedReducer ( weave , atom , value , state , space ) { const [ val , tag , bot ] = weave . referenceChain ( atom . id ) ; if ( ! tag ) { return state ; } if ( tag . atom . value . type === AuxOpType . TagMask ) { return tagMaskValueAtomAddedReducer ( weave , atom , value , < WeaveNode < TagMaskOp > > tag , state , space ) ; } if ( ! bot ) { return state ; } if ( bot . atom . value . type !== AuxOpType . Bot ) { return state ; } if ( tag . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( ! hasValue ( tag . atom . value . name ) ) { return state ; } const tagName = tag . atom . value . name ; const firstValue = first ( iterateCausalGroup ( tag ) ) ; if ( firstValue && firstValue . atom . hash !== atom . hash ) { return state ; } const id = bot . atom . value . id ; const isDeleted = isBotDeleted ( bot ) ; if ( isDeleted ) { return state ; } const sibling = first ( iterateSiblings ( firstValue ) ) ; if ( sibling && sibling . atom . value . type === AuxOpType . Value ) { const certificates = weave . roots . filter ( ( r ) => r . atom . value . type === AuxOpType . Certificate ) ; const signature = certificates . find ( ( cert ) => { for ( let node of iterateChildren ( cert ) ) { if ( node . atom . value . type === AuxOpType . Signature && node . atom . value . valueHash === sibling . atom . hash ) { return true ; } } return false ; } ) ; if ( signature ) { lodashMerge ( state , { [ id ] : { signatures : { [ tagValueHash ( id , tagName , sibling . atom . value . value ) ] : null , } , } , } ) ; } } if ( ! hasValue ( value . value ) ) { lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : null as any , } , } , } ) ; return state ; } lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : value . value , } , } , } ) ; return state ; } function insertAtomAddedReducer ( weave , atom , op , state , space ) { const { tag : tagOrValue , bot : botOrTagMask , value , } = getTextEditNodes ( weave , atom ) ; if ( ! tagOrValue ) { return state ; } if ( ! botOrTagMask ) { return state ; } if ( botOrTagMask . atom . value . type === AuxOpType . TagMask ) { return insertTagMaskAtomAddedReducer ( weave , atom , op , state , space , botOrTagMask as WeaveNode < TagMaskOp > , tagOrValue ) ; } else if ( botOrTagMask . atom . value . type !== AuxOpType . Bot ) { return state ; } const id = botOrTagMask . atom . value . id ; if ( tagOrValue . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( ! hasValue ( tagOrValue . atom . value . name ) ) { return state ; } const tagName = tagOrValue . atom . value . name ; const nodes = [ value , ... iterateCausalGroup ( value ) ] ; const edits = calculateOrderedEdits ( nodes ) ; let count = 0 ; for ( let e of edits ) { if ( e . node . atom === atom ) { break ; } count += e . text . length ; } let ops = [ ] as TagEditOp [ ] ; if ( count > 0 ) { ops . push ( preserve ( count ) ) ; } ops . push ( insert ( op . text ) ) ; const existingValue = state ?. [ id ] ?. tags ?. [ tagName ] ; if ( isTagEdit ( existingValue ) ) { lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : mergeEdits ( existingValue , edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ) , } , } , } ) ; } else { let tagEdit = edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ; let finalValue ; if ( hasValue ( existingValue ) ) { finalValue = applyTagEdit ( existingValue , tagEdit ) ; } else { finalValue = tagEdit ; } lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : finalValue , } , } , } ) ; } return state ; } function insertTagMaskAtomAddedReducer ( weave , atom , op , state , space , tagMask , value ) { if ( ! hasValue ( tagMask . atom . value . botId ) ) { return state ; } if ( ! hasValue ( tagMask . atom . value . name ) ) { return state ; } const id = tagMask . atom . value . botId ; const tagName = tagMask . atom . value . name ; if ( value . atom . value . type !== AuxOpType . Value ) { return state ; } const nodes = [ value , ... iterateCausalGroup ( value ) ] ; const edits = calculateOrderedEdits ( nodes ) ; let count = 0 ; for ( let e of edits ) { if ( e . node . atom === atom ) { break ; } count += e . text . length ; } let ops = [ ] as TagEditOp [ ] ; if ( count > 0 ) { ops . push ( preserve ( count ) ) ; } ops . push ( insert ( op . text ) ) ; const existingValue = state ?. [ id ] ?. masks ?. [ space ] ?. [ tagName ] ; if ( isTagEdit ( existingValue ) ) { lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : mergeEdits ( existingValue , edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ) , } , } , } , } ) ; } else { let tagEdit = edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ; let finalValue ; if ( hasValue ( existingValue ) ) { finalValue = applyTagEdit ( existingValue , tagEdit ) ; } else { finalValue = tagEdit ; } lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : finalValue , } , } , } , } ) ; } return state ; } function deleteTextReducer ( weave , atom , value , state , space ) { const { tag : tagOrValue , bot : botOrTagMask , values , value : valueNode , } = getTextEditNodes ( weave , atom ) ; if ( ! tagOrValue ) { return state ; } if ( ! botOrTagMask ) { return state ; } if ( botOrTagMask . atom . value . type === AuxOpType . TagMask ) { return deleteTagMaskTextReducer ( weave , atom , value , state , space , botOrTagMask as WeaveNode < TagMaskOp > , tagOrValue , values ) ; } else if ( botOrTagMask . atom . value . type !== AuxOpType . Bot ) { return state ; } if ( botOrTagMask . atom . value . type !== AuxOpType . Bot ) { return state ; } if ( tagOrValue . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( ! hasValue ( tagOrValue . atom . value . name ) ) { return state ; } const tagName = tagOrValue . atom . value . name ; const id = botOrTagMask . atom . value . id ; const nodes = [ valueNode , ... iterateCausalGroup ( valueNode ) ] ; const filtered = nodes . filter ( ( n ) => ! idEquals ( n . atom . id , atom . id ) ) ; const edits = calculateOrderedEdits ( filtered , true ) ; let ops = [ ] as TagEditOp [ ] ; for ( let { count , length } of findDeletePoints ( edits , atom as Atom < DeleteOp > , value ) ) { if ( count > 0 ) { ops . push ( preserve ( count ) ) ; } ops . push ( del ( length ) ) ; } if ( ops . length > 0 ) { const existingValue = state ?. [ id ] ?. tags ?. [ tagName ] ; if ( isTagEdit ( existingValue ) ) { lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : mergeEdits ( existingValue , edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ) , } , } , } ) ; } else { const tagEdit = edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ; let finalValue ; if ( hasValue ( existingValue ) ) { finalValue = applyTagEdit ( existingValue , tagEdit ) ; } else { finalValue = tagEdit ; } lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : finalValue , } , } , } ) ; } } return state ; } function deleteTagMaskTextReducer ( weave , atom , op , state , space , tagMask , value , values ) { if ( value . atom . value . type !== AuxOpType . Value ) { return state ; } if ( ! hasValue ( tagMask . atom . value . botId ) ) { return state ; } if ( ! hasValue ( tagMask . atom . value . name ) ) { return state ; } const tagName = tagMask . atom . value . name ; const id = tagMask . atom . value . botId ; const nodes = [ value , ... iterateCausalGroup ( value ) ] ; const filtered = nodes . filter ( ( n ) => ! idEquals ( n . atom . id , atom . id ) ) ; const edits = calculateOrderedEdits ( filtered , true ) ; let ops = [ ] as TagEditOp [ ] ; for ( let { count , length } of findDeletePoints ( edits , atom as Atom < DeleteOp > , op ) ) { if ( count > 0 ) { ops . push ( preserve ( count ) ) ; } ops . push ( del ( length ) ) ; } if ( ops . length > 0 ) { const existingValue = state ?. [ id ] ?. masks ?. [ space ] ?. [ tagName ] ; if ( isTagEdit ( existingValue ) ) { lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : mergeEdits ( existingValue , edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ) , } , } , } , } ) ; } else { const tagEdit = edit ( { [ atom . id . site ] : atom . id . timestamp } , ... ops ) ; let finalValue ; if ( hasValue ( existingValue ) ) { finalValue = applyTagEdit ( existingValue , tagEdit ) ; } else { finalValue = tagEdit ; } lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : finalValue , } , } , } , } ) ; } } return state ; } param edits param atom param value export function * findDeletePoints ( edits , atom , value ) { let count = 0 ; let nodeCount = 0 ; let length = 0 ; let createdDelete = false ; for ( let e of edits ) { let removedText = false ; if ( idEquals ( e . node . atom . id , atom . cause ) ) { if ( nodeCount + e . marked . length > value . start ) { for ( let i = 0 ; i < e . marked . length && i < value . end - e . offset ; i ++ ) { const char = e . marked [ i ] ; if ( i >= value . start - e . offset ) { if ( char !== '\0' ) { length += 1 ; } } else { if ( char === '\0' ) { count -= 1 ; } } } if ( length > 0 ) { if ( ! createdDelete ) { count += value . start - e . offset ; } createdDelete = true ; removedText = true ; yield { count , length , } ; length = 0 ; count = 0 ; } } nodeCount += e . marked . length ; } if ( ! removedText ) { count += e . text . length ; } } } param weave param node param offset param length function calculateSiblingOffset ( weave , node , offset , length ) { const parent = weave . getNode ( node . atom . cause ) ; const children = [ ... iterateChildren ( parent ) ] ; let count = 0 ; let overlap = 0 ; let newer = true ; for ( let n of children ) { if ( n === node ) { newer = false ; continue ; } if ( n . atom . value . type === AuxOpType . Insert ) { if ( ! newer && n . atom . value . index <= offset ) { count += n . atom . value . text . length ; } } else if ( n . atom . value . type === AuxOpType . Delete ) { if ( n . atom . value . start <= offset ) { const deleteStart = n . atom . value . start ; const deleteEnd = n . atom . value . end ; const deleteCount = deleteEnd - deleteStart ; const shrink = - deleteCount ; const nodeStart = offset ; const nodeEnd = offset + length ; let startOffset = 0 ; if ( node . atom . value . type === AuxOpType . Insert && nodeStart <= deleteEnd ) { startOffset = offset - n . atom . value . start ; if ( startOffset === 0 ) { startOffset = deleteCount ; } } const endOffset = 0 ; const startOverlap = Math . max ( 0 , deleteStart - nodeStart ) ; const endOverlap = Math . max ( 0 , nodeEnd - deleteEnd ) ; const totalOverlap = Math . max ( 0 , length - ( startOverlap + endOverlap ) ) ; overlap += totalOverlap ; count += startOffset + shrink ; } } } return { offset : count , overlap } ; } /**
 * Gets the weave nodes needed for a text edit.
 * Returns the bot node, tag node, and value/insert nodes.
 */ export function getTextEditNodes ( weave , atom ) { const nodes = weave . referenceChain ( atom . id ) ; const bot = nodes [ nodes . length - 1 ] ; const value = nodes [ nodes . length - 3 ] ; const tag = nodes [ nodes . length - 2 ] ; const values = bot . atom . value . type === AuxOpType . Bot ? nodes . slice ( 0 , nodes . length - 2 ) : nodes . slice ( 0 , nodes . length - 1 ) ; return { tag , bot , value , values , } ; } function tagMaskValueAtomAddedReducer ( weave , atom , value , tag , state , space ) { if ( ! hasValue ( space ) ) { return state ; } if ( ! hasValue ( tag . atom . value . name ) ) { return state ; } const tagName = tag . atom . value . name ; const id = tag . atom . value . botId ; const firstValue = first ( iterateCausalGroup ( tag ) ) ; if ( firstValue && firstValue . atom . hash !== atom . hash ) { return state ; } if ( ! hasValue ( value . value ) ) { lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : null as any , } , } , } , } ) ; return state ; } lodashMerge ( state , { [ id ] : { masks : { [ space ] : { [ tagName ] : value . value , } , } , } , } ) ; return state ; } function deleteAtomAddedReducer ( weave , atom , value , state , space ) { const parent = weave . getNode ( atom . cause ) ; if ( ! parent ) { return state ; } if ( parent . atom . value . type === AuxOpType . Bot ) { return deleteBotReducer ( weave , < WeaveNode < BotOp > > parent , atom , state ) ; } else if ( parent . atom . value . type === AuxOpType . Insert || parent . atom . value . type === AuxOpType . Value ) { return deleteTextReducer ( weave , atom , value , state , space ) ; } return state ; } function certificateAtomAddedReducer ( weave , atom , value , state ) { const botId = certificateId ( atom ) ; let signerId ; if ( atom . cause ) { if ( ! isCertificateChainValid ( weave , atom ) ) { return state ; } signerId = certificateId ( weave . getNode ( atom . cause ) . atom ) ; } else { if ( ! validateCertSignature ( null , atom ) ) { return state ; } signerId = botId ; } lodashMerge ( state , { [ botId ] : createBot ( botId , { keypair : value . keypair , signature : value . signature , signingCertificate : signerId , atom : atom , } , CERTIFIED_SPACE ) , } ) ; return state ; } function isCertificateChainValid ( weave , atom ) { const chain = weave . referenceChain ( atom . cause ) ; let i = 0 ; let signee = atom ; while ( i < chain . length ) { let signer = chain [ i ] ; if ( signer . atom . value . type !== AuxOpType . Certificate ) { return false ; } const signerCert = < Atom < CertificateOp > > signer . atom ; if ( ! validateCertSignature ( signerCert , signee ) ) { return false ; } if ( isCertDirectlyRevoked ( weave , signer ) ) { return false ; } signee = signerCert ; i ++ ; } if ( ! ! signee . cause ) { return false ; } if ( ! validateCertSignature ( null , signee ) ) { return false ; } let signeeRef = weave . getNode ( signee . id ) ; if ( isCertDirectlyRevoked ( weave , signeeRef ) ) { return false ; } return true ; } function revokeAtomAddedReducer ( weave , atom , value , state ) { const parent = weave . getNode ( atom . cause ) ; if ( ! parent ) { return state ; } if ( parent . atom . value . type === AuxOpType . Certificate ) { if ( ! isRevocationValid ( weave , < Atom < RevocationOp > > atom , < WeaveNode < CertificateOp > > parent ) ) { return state ; } return certificateRemovedAtomReducer ( weave , parent . atom , parent . atom . value , parent , state ) ; } else if ( parent . atom . value . type === AuxOpType . Signature ) { if ( ! isRevocationValid ( weave , < Atom < RevocationOp > > atom , < WeaveNode < SignatureOp > > parent ) ) { return state ; } return signatureRemovedAtomReducer ( weave , parent . atom , parent . atom . value , state ) ; } return state ; } function signatureAtomAddedReducer ( weave , atom , value , state ) { const parent = weave . getNode ( atom . cause ) ; if ( ! parent ) { return state ; } if ( parent . atom . value . type !== AuxOpType . Certificate ) { return state ; } const cert = < Atom < CertificateOp > > parent . atom ; const signature = < Atom < SignatureOp > > atom ; const [ val , tag , bot ] = weave . referenceChain ( value . valueId ) ; if ( ! val || ! tag || ! bot ) { return state ; } if ( val . atom . hash !== value . valueHash ) { return state ; } if ( bot . atom . value . type !== AuxOpType . Bot ) { return state ; } if ( tag . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( ! hasValue ( tag . atom . value . name ) ) { return state ; } const tagName = tag . atom . value . name ; const id = bot . atom . value . id ; const isDeleted = isBotDeleted ( bot ) ; if ( isDeleted ) { return state ; } if ( ! isCertificateChainValid ( weave , cert ) ) { return state ; } const realValue = < Atom < ValueOp > > val . atom ; if ( ! validateSignedValue ( cert , signature , realValue ) ) { return state ; } const hash = tagValueHash ( id , tagName , realValue . value . value ) ; lodashMerge ( state , { [ id ] : { signatures : { [ hash ] : tagName , } , } , } ) ; return state ; } param weave param cert function isCertDirectlyRevoked ( weave , cert ) { for ( let child of iterateChildren ( cert ) ) { if ( child . atom . value . type === AuxOpType . Revocation && isRevocationValid ( weave , < Atom < RevocationOp > > child . atom , < WeaveNode < CertificateOp > > cert ) ) { return true ; } } return false ; } function isRevocationValid ( weave , revocation , parent ) { const chain = weave . referenceChain ( parent . atom . id ) ; let signingCert ; for ( let node of chain ) { if ( node . atom . value . type === AuxOpType . Certificate && node . atom . hash === revocation . value . certHash ) { signingCert = < WeaveNode < CertificateOp > > node ; break ; } } if ( ! signingCert ) { return false ; } if ( ! validateRevocation ( signingCert . atom , revocation , parent . atom ) ) { return false ; } return true ; } function deleteBotReducer ( weave , bot , atom , state ) { const firstValue = first ( iterateCausalGroup ( bot ) ) ; if ( firstValue && firstValue . atom . hash !== atom . hash ) { return state ; } const id = bot . atom . value . id ; return deleteBot ( weave , id , state ) ; } function conflictReducer ( weave , result , state , space , initial ) { if ( ! result . loserRef ) { return state ; } let update = state ; if ( result . loser . value . type === AuxOpType . Bot ) { for ( let node of iterateChildren ( result . loserRef ) ) { if ( node . atom . value . type === AuxOpType . Tag ) { deleteTag ( node . atom , result . loser , update ) ; } } } else if ( result . loser . value . type === AuxOpType . Tag ) { const bot = weave . getNode ( result . loser . cause ) . atom ; if ( bot . value . type === AuxOpType . Bot ) { deleteTag ( result . loser , < Atom < BotOp > > bot , update ) ; } } else if ( result . loser . value . type === AuxOpType . Certificate ) { certificateRemovedAtomReducer ( weave , result . loser , result . loser . value , result . loserRef , update ) ; } else if ( result . loser . value . type === AuxOpType . Revocation ) { revocationRemovedAtomReducer ( weave , result . loser , result . loser . value , update ) ; } else if ( result . loser . value . type === AuxOpType . Signature ) { signatureRemovedAtomReducer ( weave , result . loser , result . loser . value , update ) ; } atomAddedReducer ( weave , result . winner , update , space , initial ) ; return update ; } function addBot ( atom , botId , state , space ) { if ( atom . cause !== null ) { return state ; } lodashMerge ( state , { [ botId ] : createBot ( botId , undefined , < BotSpace > space ) , } ) ; return state ; } function deleteBot ( weave , id , state ) { lodashMerge ( state , { [ id ] : null , } ) ; return state ; } function deleteTag ( tag , bot , state ) { const value = tag . value as TagOp ; const id = bot . value . id ; lodashMerge ( state , { [ id ] : { tags : { [ value . name ] : null , } , } , } ) ; return state ; } function isBotDeleted ( bot ) { const firstValue = first ( iterateCausalGroup ( bot ) ) ; return firstValue . atom . value . type === AuxOpType . Delete ; } function removeAtom ( weave , atom , node , state ) { if ( atom . value . type === AuxOpType . Bot ) { return deleteBot ( weave , atom . value . id , state ) ; } else if ( atom . value . type === AuxOpType . Value ) { return valueRemovedAtomReducer ( weave , atom , atom . value , state ) ; } else if ( atom . value . type === AuxOpType . Certificate ) { return certificateRemovedAtomReducer ( weave , atom , atom . value , node , state ) ; } else if ( atom . value . type === AuxOpType . Revocation ) { return revocationRemovedAtomReducer ( weave , atom , atom . value , state ) ; } else if ( atom . value . type === AuxOpType . Signature ) { return signatureRemovedAtomReducer ( weave , atom , atom . value , state ) ; } else { return state ; } } function valueRemovedAtomReducer ( weave , atom , value , state ) { const [ tag , bot ] = weave . referenceChain ( atom . cause ) ; if ( ! tag || ! bot ) { return state ; } if ( bot . atom . value . type !== AuxOpType . Bot ) { return state ; } if ( tag . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( ! hasValue ( tag . atom . value . name ) ) { return state ; } const isDeleted = isBotDeleted ( bot ) ; if ( isDeleted ) { return state ; } const tagName = tag . atom . value . name ; const id = bot . atom . value . id ; const firstValue = first ( iterateCausalGroup ( tag ) ) ; if ( firstValue && firstValue . atom . value . type === AuxOpType . Value && firstValue . atom . hash !== atom . hash ) { if ( firstValue . atom . id . timestamp <= atom . id . timestamp ) { lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : firstValue . atom . value . value , } , } , } ) ; return state ; } else { return state ; } } lodashMerge ( state , { [ id ] : { tags : { [ tagName ] : null , } , } , } ) ; return state ; } function certificateRemovedAtomReducer ( weave , atom , value , node , state ) { const id = certificateId ( atom ) ; lodashMerge ( state , { [ id ] : null , } ) ; for ( let child of iterateCausalGroup ( node ) ) { if ( child . atom . value . type === AuxOpType . Certificate ) { certificateRemovedAtomReducer ( weave , child . atom , child . atom . value , child , state ) ; } else if ( child . atom . value . type === AuxOpType . Signature ) { signatureRemovedAtomReducer ( weave , child . atom , child . atom . value , state ) ; } } return state ; } function signatureRemovedAtomReducer ( weave , atom , value , state ) { const [ val , tag , bot ] = weave . referenceChain ( value . valueId ) ; if ( ! val || ! tag || ! bot ) { return state ; } if ( val . atom . hash !== value . valueHash ) { return state ; } if ( val . atom . value . type !== AuxOpType . Value ) { return state ; } if ( tag . atom . value . type !== AuxOpType . Tag ) { return state ; } if ( bot . atom . value . type !== AuxOpType . Bot ) { return state ; } const hash = tagValueHash ( bot . atom . value . id , tag . atom . value . name , val . atom . value . value ) ; lodashMerge ( state , { [ bot . atom . value . id ] : { signatures : { [ hash ] : null , } , } , } ) ; return state ; } function revocationRemovedAtomReducer ( weave , atom , value , state ) { if ( ! atom . cause ) { return state ; } const parent = weave . getNode ( atom . cause ) ; if ( ! parent ) { return state ; } if ( parent . atom . value . type === AuxOpType . Certificate ) { return certificateAtomAddedReducer ( weave , < Atom < CertificateOp > > parent . atom , parent . atom . value , state ) ; } return state ; } export function certificateId ( atom ) { return uuidv5 ( atom . hash , CERT_ID_NAMESPACE ) ; }