@@ -32,7 +32,7 @@ import {
3232 verifyTreecrdtOpV2 ,
3333 verifyTreecrdtRevocationRecordV1 ,
3434} from '../dist/treecrdt-auth.js' ;
35- import type { Capability , Filter , OpRef , SyncBackend } from '@treecrdt/sync-protocol' ;
35+ import type { Capability , Filter , OpAuth , OpRef , SyncBackend } from '@treecrdt/sync-protocol' ;
3636
3737ed25519Hashes . sha512 = sha512 ;
3838
@@ -458,6 +458,59 @@ test('auth: signOps selects proof_ref per op when multiple tokens exist', async
458458 ) ;
459459} ) ;
460460
461+ test ( 'auth: verifyOps is side-effect-free and onVerifiedOps publishes proof material' , async ( ) => {
462+ const docId = 'doc-auth-local-proof-staging' ;
463+ const root = '0' . repeat ( 32 ) ;
464+ const issuerSk = ed25519Utils . randomSecretKey ( ) ;
465+ const issuerPk = await getPublicKey ( issuerSk ) ;
466+ const writerSk = ed25519Utils . randomSecretKey ( ) ;
467+ const writerPk = await getPublicKey ( writerSk ) ;
468+ const token = issueTreecrdtCapabilityTokenV1 ( {
469+ issuerPrivateKey : issuerSk ,
470+ subjectPublicKey : writerPk ,
471+ docId,
472+ actions : [ 'write_structure' ] ,
473+ } ) ;
474+ const persisted : Array < { opRef : OpRef ; auth : OpAuth } > = [ ] ;
475+ const auth = createTreecrdtCoseCwtAuth ( {
476+ issuerPublicKeys : [ issuerPk ] ,
477+ localPrivateKey : writerSk ,
478+ localPublicKey : writerPk ,
479+ localCapabilityTokens : [ token ] ,
480+ requireProofRef : true ,
481+ opAuthStore : {
482+ storeOpAuth : async ( entries ) => {
483+ persisted . push ( ...entries ) ;
484+ } ,
485+ getOpAuthByOpRefs : async ( opRefs ) => opRefs . map ( ( ) => null ) ,
486+ } ,
487+ } ) ;
488+ const ctx = { docId, purpose : 'reconcile' as const , filterId : '__local__' } ;
489+ const firstProposal = makeOp ( writerPk , 1 , 1 , {
490+ type : 'insert' ,
491+ parent : root ,
492+ node : nodeIdFromInt ( 1 ) ,
493+ orderKey : orderKeyFromPosition ( 0 ) ,
494+ } ) ;
495+ const retriedProposal = makeOp ( writerPk , 1 , 2 , {
496+ type : 'insert' ,
497+ parent : root ,
498+ node : nodeIdFromInt ( 2 ) ,
499+ orderKey : orderKeyFromPosition ( 1 ) ,
500+ } ) ;
501+
502+ const firstAuth = ( await auth . signOps ?.( [ firstProposal ] , ctx ) ) ! ;
503+ await auth . verifyOps ?.( [ firstProposal ] , firstAuth , ctx ) ;
504+ const retriedAuth = ( await auth . signOps ?.( [ retriedProposal ] , ctx ) ) ! ;
505+ await auth . verifyOps ?.( [ retriedProposal ] , retriedAuth , ctx ) ;
506+ expect ( persisted ) . toHaveLength ( 0 ) ;
507+
508+ await auth . onVerifiedOps ?.( [ retriedProposal ] , retriedAuth , ctx ) ;
509+ expect ( persisted ) . toHaveLength ( 1 ) ;
510+ expect ( persisted [ 0 ] ! . auth . sig ) . toEqual ( retriedAuth [ 0 ] ! . sig ) ;
511+ expect ( persisted [ 0 ] ! . auth . sig ) . not . toEqual ( firstAuth [ 0 ] ! . sig ) ;
512+ } ) ;
513+
461514test ( 'auth: restart reuses only a retained proof for the exact local operation' , async ( ) => {
462515 const docId = 'doc-auth-retained-local-proof' ;
463516 const issuerSk = ed25519Utils . randomSecretKey ( ) ;
0 commit comments