@@ -511,12 +511,26 @@ export class CollectorClient {
511511 }
512512
513513 /**
514- * Accept a pending update request: delete old access, create new one with updated permissions,
515- * notify requester via inbox with new apiEndpoint.
514+ * Accept a pending update request: update the access in place via accesses.update
515+ * (Plan 66), notify requester via inbox.
516+ *
517+ * The access id, token and apiEndpoint are preserved across the update; only
518+ * the wire id becomes composite (`<base>:<serial>`). The requester does not
519+ * need to re-store an apiEndpoint.
520+ *
521+ * `clientData` is intentionally NOT sent: the server merges (verified empirically
522+ * on Plan 66 demo) so existing keys including legacy `hdsCollectorClient.previousAccessIds`
523+ * chains stay intact. The current `hdsCollectorClient.eventData` snapshot remains
524+ * valid because `this.eventData` is unchanged at update time.
525+ *
526+ * `StaleAccessIdError` handling: if another writer updated the access between
527+ * the load of `pendingUpdate` and now, refetch the access by name and retry
528+ * once. Two consecutive stale errors propagate.
516529 */
517530 async acceptUpdate ( ) : Promise < { accessData : any , requesterEvent : any } | null > {
518531 if ( ! this . pendingUpdate ) throw new HDSLibError ( 'No pending update to accept' ) ;
519532 if ( this . status !== CollectorClient . STATUSES . active ) throw new HDSLibError ( 'Can only accept updates on active CollectorClients' ) ;
533+ if ( ! this . accessData ?. id ) throw new HDSLibError ( 'No access to update' , this . accessData ) ;
520534
521535 const update = this . pendingUpdate . content ;
522536
@@ -527,7 +541,7 @@ export class CollectorClient {
527541 } ) ;
528542
529543 // Handle chat feature if requested
530- const responseContent : { apiEndpoint ?: string , chat ?: any } = { } ;
544+ const responseContent : { chat ?: any } = { } ;
531545 if ( update . features ?. chat && ! this . hasChatFeature ) {
532546 const chatStreamMain = `chat-${ this . requesterUsername } ` ;
533547 const chatStreamIncoming = `chat-${ this . requesterUsername } -in` ;
@@ -559,44 +573,30 @@ export class CollectorClient {
559573 ) ;
560574 }
561575
562- // Collect previous access IDs for event attribution (modifiedBy tracking)
563- const previousAccessIds : string [ ] = [ ] ;
564- if ( this . accessData ) {
565- if ( this . accessData . id ) previousAccessIds . push ( this . accessData . id ) ;
566- // Chain: carry forward any IDs from the old access's clientData
567- const oldPrevIds = this . accessData . clientData ?. hdsCollectorClient ?. previousAccessIds ;
568- if ( Array . isArray ( oldPrevIds ) ) {
569- for ( const id of oldPrevIds ) {
570- if ( ! previousAccessIds . includes ( id ) ) previousAccessIds . push ( id ) ;
576+ // Update access in place. 1-retry on StaleAccessIdError.
577+ let attempt = 0 ;
578+ while ( true ) {
579+ try {
580+ const updated = await ( this . app . connection as any ) . updateAccess ( this . accessData . id , {
581+ permissions : cleanedPermissions
582+ } ) ;
583+ this . accessData = updated ;
584+ break ;
585+ } catch ( e : any ) {
586+ if ( e instanceof ( pryv as any ) . StaleAccessIdError && attempt === 0 ) {
587+ attempt ++ ;
588+ // Refetch the access by name (composite serial may have advanced)
589+ const accesses = await this . app . connection . apiOne ( 'accesses.get' , { } , 'accesses' ) ;
590+ const fresh = accesses . find ( ( a : any ) => a . name === this . key ) ;
591+ if ( ! fresh ) throw new HDSLibError ( 'Access disappeared during update' , accesses ) ;
592+ this . accessData = fresh ;
593+ continue ;
571594 }
595+ throw e ;
572596 }
573597 }
574598
575- // Delete old access
576- if ( this . accessData && ! this . accessData . deleted ) {
577- await this . app . connection . apiOne ( 'accesses.delete' , { id : this . accessData . id } , 'accessDeletion' ) ;
578- }
579-
580- // Create new access with updated permissions
581- const accessCreateData = {
582- name : this . key ,
583- type : 'shared' ,
584- permissions : cleanedPermissions ,
585- clientData : {
586- hdsCollectorClient : {
587- version : 0 ,
588- eventData : this . eventData ,
589- previousAccessIds
590- }
591- }
592- } ;
593- const accessData = await this . app . connection . apiOne ( 'accesses.create' , accessCreateData , 'access' ) ;
594- this . accessData = accessData ;
595- if ( ! this . accessData ?. apiEndpoint ) throw new HDSLibError ( 'Failed creating updated access' , accessData ) ;
596-
597- responseContent . apiEndpoint = this . accessData . apiEndpoint ;
598-
599- // Notify requester via inbox
599+ // Notify requester via inbox. No `apiEndpoint` field — token is preserved.
600600 const requesterEvent = await this . #updateRequester( 'update-accept' , responseContent ) ;
601601 if ( requesterEvent != null ) {
602602 this . pendingUpdate = null ;
0 commit comments