eidetica 0.2.0

Decentralized DB. Remember Everything. Everywhere. All At Once.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
//! Sync request handler trait and implementation.
//!
//! This module contains transport-agnostic handlers that process
//! sync requests and generate responses. These handlers can be
//! used by any transport implementation through the SyncHandler trait.

use async_trait::async_trait;
use tracing::{Instrument, debug, error, info, info_span, trace, warn};

use super::{
    DEVICE_KEY_NAME,
    bootstrap_request_manager::{
        BootstrapRequest, BootstrapRequestManager, RequestStatus, current_timestamp,
    },
    peer_manager::PeerManager,
    peer_types::Address,
    protocol::{
        BootstrapResponse, HandshakeRequest, HandshakeResponse, IncrementalResponse,
        PROTOCOL_VERSION, RequestContext, SyncRequest, SyncResponse, SyncTreeRequest, TreeInfo,
    },
    user_sync_manager::UserSyncManager,
};
use crate::{
    Database, Instance, Result,
    auth::{
        KeyStatus, Permission,
        crypto::{create_challenge_response, format_public_key, generate_challenge},
    },
    entry::ID,
    store::SettingsStore,
    sync::error::SyncError,
};

/// Trait for handling sync requests with database access.
///
/// Implementations of this trait can process sync requests and generate
/// appropriate responses, with full access to the database backend for
/// storing and retrieving entries.
#[async_trait]
pub trait SyncHandler: Send + std::marker::Sync {
    /// Handle a sync request and generate an appropriate response.
    ///
    /// This is the main entry point for processing sync messages,
    /// regardless of which transport they arrived through.
    ///
    /// # Arguments
    /// * `request` - The sync request to process
    /// * `context` - Context about the request (remote address, etc.)
    ///
    /// # Returns
    /// The appropriate response for the given request.
    async fn handle_request(&self, request: &SyncRequest, context: &RequestContext)
    -> SyncResponse;
}

/// Default implementation of SyncHandler with database backend access.
pub struct SyncHandlerImpl {
    instance: crate::WeakInstance,
    sync_tree_id: ID,
}

impl SyncHandlerImpl {
    /// Create a new SyncHandlerImpl with the given instance.
    ///
    /// # Arguments
    /// * `instance` - Database instance for storing and retrieving entries
    /// * `sync_tree_id` - Root ID of the sync database for storing bootstrap requests
    pub fn new(instance: Instance, sync_tree_id: ID) -> Self {
        Self {
            instance: instance.downgrade(),
            sync_tree_id,
        }
    }

    /// Upgrade the weak instance reference to a strong reference.
    fn instance(&self) -> Result<Instance> {
        self.instance
            .upgrade()
            .ok_or_else(|| SyncError::InstanceDropped.into())
    }

    /// Get access to the sync tree for bootstrap request management.
    ///
    /// # Returns
    /// A Database instance for the sync tree with device key authentication.
    fn get_sync_tree(&self) -> Result<Database> {
        // Load sync tree with the device key
        let instance = self.instance()?;
        let signing_key = instance
            .backend()
            .get_private_key(DEVICE_KEY_NAME)?
            .ok_or_else(|| SyncError::DeviceKeyNotFound {
                key_name: DEVICE_KEY_NAME.to_string(),
            })?;

        Database::open(
            self.instance()?,
            &self.sync_tree_id,
            signing_key,
            DEVICE_KEY_NAME.to_string(),
        )
    }

    /// Store a bootstrap request in the sync database for manual approval.
    ///
    /// # Arguments
    /// * `tree_id` - ID of the tree being requested
    /// * `requesting_key` - Public key of the requesting device
    /// * `requesting_key_name` - Name of the requesting key
    /// * `requested_permission` - Permission level being requested
    ///
    /// # Returns
    /// The generated UUID for the stored request
    async fn store_bootstrap_request(
        &self,
        tree_id: &ID,
        requesting_key: &str,
        requesting_key_name: &str,
        requested_permission: &crate::auth::Permission,
    ) -> crate::Result<String> {
        let sync_tree = self.get_sync_tree()?;
        let op = sync_tree.new_transaction()?;
        let manager = BootstrapRequestManager::new(&op);

        let request = BootstrapRequest {
            tree_id: tree_id.clone(),
            requesting_pubkey: requesting_key.to_string(),
            requesting_key_name: requesting_key_name.to_string(),
            requested_permission: requested_permission.clone(),
            timestamp: current_timestamp(),
            status: RequestStatus::Pending,
            // TODO: We need to get the actual peer address from the transport layer
            // For now, use a placeholder that will need to be fixed when implementing notifications
            peer_address: Address {
                transport_type: "unknown".to_string(),
                address: "unknown".to_string(),
            },
        };

        let request_id = manager.store_request(request)?;
        op.commit()?;

        Ok(request_id)
    }
}

#[async_trait]
impl SyncHandler for SyncHandlerImpl {
    async fn handle_request(
        &self,
        request: &SyncRequest,
        context: &RequestContext,
    ) -> SyncResponse {
        match request {
            SyncRequest::Handshake(handshake_req) => {
                debug!("Received handshake request");
                self.handle_handshake(handshake_req, context).await
            }
            SyncRequest::SyncTree(sync_req) => {
                debug!(tree_id = %sync_req.tree_id, tips_count = sync_req.our_tips.len(), "Received sync tree request");
                self.handle_sync_tree(sync_req, context).await
            }
            SyncRequest::SendEntries(entries) => {
                // Process and store the received entries
                let count = entries.len();
                info!(count = count, "Received entries for synchronization");

                // Get instance once before loop
                let instance = match self.instance() {
                    Ok(i) => i,
                    Err(e) => return SyncResponse::Error(format!("Instance dropped: {e}")),
                };

                // Store entries in the backend as unverified (from sync)
                let mut stored_count = 0usize;
                for entry in entries {
                    match instance.backend().put_unverified(entry.clone()) {
                        Ok(_) => {
                            stored_count += 1;
                            trace!(entry_id = %entry.id(), "Stored entry successfully");
                        }
                        Err(e) => {
                            error!(entry_id = %entry.id(), error = %e, "Failed to store entry");
                            // Continue processing other entries rather than failing completely
                        }
                    }
                }

                debug!(
                    received = count,
                    stored = stored_count,
                    "Completed entry synchronization"
                );
                if count <= 1 {
                    SyncResponse::Ack
                } else {
                    SyncResponse::Count(stored_count)
                }
            }
        }
    }
}

impl SyncHandlerImpl {
    /// Get the highest permission level a key has in the database's auth settings.
    ///
    /// This looks up all permissions the key has (direct + global wildcard) and returns
    /// the highest one. Used for auto-detecting permissions during bootstrap.
    ///
    /// # Arguments
    /// * `tree_id` - The database/tree ID to check auth settings for
    /// * `requesting_pubkey` - The public key to look up
    ///
    /// # Returns
    /// - `Ok(Some(Permission))` if key has any permissions
    /// - `Ok(None)` if key not found in auth settings
    /// - `Err` if database access fails
    async fn get_key_highest_permission(
        &self,
        tree_id: &ID,
        requesting_pubkey: &str,
    ) -> Result<Option<Permission>> {
        let database = Database::open_readonly(tree_id.clone(), &self.instance()?)?;
        let transaction = database.new_transaction()?;
        let settings_store = SettingsStore::new(&transaction)?;
        let auth_settings = settings_store.get_auth_settings()?;

        let results = auth_settings.find_all_sigkeys_for_pubkey(requesting_pubkey);

        if results.is_empty() {
            return Ok(None);
        }

        // Results are sorted highest first, so take the first one
        Ok(Some(results[0].1.clone()))
    }

    /// Check if the requesting key already has sufficient permissions through existing auth.
    ///
    /// This uses the AuthSettings.can_access() method to check if the requesting key
    /// already has sufficient permissions (including through global '*' permissions).
    ///
    /// # Arguments
    /// * `tree_id` - The database/tree ID to check auth settings for
    /// * `requesting_pubkey` - The public key making the request
    /// * `requested_permission` - The permission level being requested
    ///
    /// # Returns
    /// - `Ok(true)` if key has sufficient permission
    /// - `Ok(false)` if key lacks sufficient permission or auth check fails
    async fn check_existing_auth_permission(
        &self,
        tree_id: &ID,
        requesting_pubkey: &str,
        requested_permission: &Permission,
    ) -> Result<bool> {
        // FIXME: This should not be using the device key for auth checks
        // Load database with device key for accessing settings
        let instance = self.instance()?;
        let signing_key = instance
            .backend()
            .get_private_key(DEVICE_KEY_NAME)?
            .ok_or_else(|| SyncError::DeviceKeyNotFound {
                key_name: DEVICE_KEY_NAME.to_string(),
            })?;

        let database = Database::open(
            self.instance()?,
            tree_id,
            signing_key,
            DEVICE_KEY_NAME.to_string(),
        )?;
        let transaction = database.new_transaction()?;
        let settings_store = SettingsStore::new(&transaction)?;

        let auth_settings = settings_store.get_auth_settings()?;

        // Use the AuthSettings.can_access() method to check permissions
        if auth_settings.can_access(requesting_pubkey, requested_permission) {
            debug!(
                tree_id = %tree_id,
                requesting_pubkey = %requesting_pubkey,
                requested_permission = ?requested_permission,
                "Key has sufficient permission for bootstrap access"
            );
            return Ok(true);
        }

        Ok(false)
    }

    /// Check if a database requires authentication for unauthenticated requests.
    ///
    /// This method checks if the database requires authentication for bootstrap requests
    /// that don't provide credentials. A database allows unauthenticated access if:
    /// 1. It has no auth settings configured at all (empty auth), OR
    /// 2. It has a global `*` permission configured that allows unauthenticated access
    ///
    /// # Arguments
    /// * `tree_id` - The database/tree ID to check auth configuration for
    ///
    /// # Returns
    /// - `Ok(true)` if database requires authentication (has auth but no global permission)
    /// - `Ok(false)` if database allows unauthenticated access (no auth or has global permission)
    /// - `Err` if the check fails
    async fn check_if_database_has_auth(&self, tree_id: &ID) -> Result<bool> {
        let database = Database::open_readonly(tree_id.clone(), &self.instance()?)?;
        let transaction = database.new_transaction()?;
        let settings_store = SettingsStore::new(&transaction)?;

        let auth_settings = settings_store.get_auth_settings()?;

        // Check if auth settings is completely empty (no auth configured)
        if auth_settings.as_doc().as_hashmap().is_empty() {
            debug!(
                tree_id = %tree_id,
                "Database has no auth configured - allowing unauthenticated access"
            );
            return Ok(false); // No auth required
        }

        // Auth is configured - check if there's an Active global "*" permission
        if let Ok(global_key) = auth_settings.get_key("*")
            && *global_key.status() == KeyStatus::Active
        {
            debug!(
                tree_id = %tree_id,
                global_permission = ?global_key.permissions(),
                "Database has global '*' permission - allowing unauthenticated access"
            );
            return Ok(false); // Global permission allows unauthenticated access
        }

        // Auth is configured but no global permission - require authentication
        debug!(
            tree_id = %tree_id,
            "Database has auth configured without global permission - requiring authentication"
        );
        Ok(true) // Auth required
    }

    /// Check if a database has sync enabled by at least one user.
    ///
    /// This is a security-critical check that determines if a database should accept
    /// any sync requests at all. A database is only eligible for sync if at least one
    /// user has it in their preferences with `sync_enabled: true`.
    ///
    /// # Security
    /// This method implements fail-closed behavior:
    /// - Returns `false` on any error (no information leakage)
    /// - Returns `false` if no users have the database in preferences
    /// - Returns `false` if combined_settings.sync_enabled is false
    /// - Only returns `true` if explicitly enabled
    ///
    /// # Arguments
    /// * `tree_id` - The ID of the database to check
    ///
    /// # Returns
    /// `true` if the database has sync enabled, `false` otherwise (including errors)
    async fn is_database_sync_enabled(&self, tree_id: &ID) -> bool {
        let instance = match self.instance() {
            Ok(i) => i,
            Err(_) => return false, // Fail closed
        };

        let signing_key = match instance.backend().get_private_key(DEVICE_KEY_NAME) {
            Ok(Some(key)) => key,
            _ => return false, // Fail closed
        };

        let sync_database = match Database::open(
            instance.clone(),
            &self.sync_tree_id,
            signing_key,
            DEVICE_KEY_NAME.to_string(),
        ) {
            Ok(db) => db,
            Err(_) => return false, // Fail closed
        };

        let transaction = match sync_database.new_transaction() {
            Ok(tx) => tx,
            Err(_) => return false, // Fail closed
        };

        // Use UserSyncManager to get combined settings
        let user_mgr = UserSyncManager::new(&transaction);
        match user_mgr.get_combined_settings(tree_id) {
            Ok(Some(settings)) => settings.sync_enabled,
            _ => false, // Fail closed: no settings or error
        }
    }

    /// Register an incoming peer and add their addresses to the peer list.
    ///
    /// This method registers a peer that initiated a connection to us during handshake.
    /// It adds both the peer-advertised addresses and the transport-provided remote address.
    ///
    /// # Arguments
    /// * `peer_pubkey` - The peer's public key
    /// * `display_name` - Optional display name for the peer
    /// * `advertised_addresses` - Addresses the peer advertised in their handshake
    /// * `remote_address` - The actual address from which the connection originated
    ///
    /// # Returns
    /// Result indicating success or failure of registration
    fn register_incoming_peer(
        &self,
        peer_pubkey: &str,
        display_name: Option<&str>,
        advertised_addresses: &[Address],
        remote_address: &Option<Address>,
    ) -> Result<()> {
        let sync_tree = self.get_sync_tree()?;
        let op = sync_tree.new_transaction()?;
        let peer_manager = PeerManager::new(&op);

        // Try to register the peer (ignore if already exists)
        match peer_manager.register_peer(peer_pubkey, display_name) {
            Ok(()) => {
                info!(peer_pubkey = %peer_pubkey, "Registered new incoming peer");
            }
            Err(crate::Error::Sync(crate::sync::error::SyncError::PeerAlreadyExists(_))) => {
                debug!(peer_pubkey = %peer_pubkey, "Peer already registered, updating addresses");
            }
            Err(e) => return Err(e),
        }

        // Add all advertised addresses
        for addr in advertised_addresses {
            if let Err(e) = peer_manager.add_address(peer_pubkey, addr.clone()) {
                warn!(peer_pubkey = %peer_pubkey, address = ?addr, error = %e, "Failed to add advertised address");
            }
        }

        // Add the remote address from transport if available
        if let Some(addr) = remote_address
            && let Err(e) = peer_manager.add_address(peer_pubkey, addr.clone())
        {
            warn!(peer_pubkey = %peer_pubkey, address = ?addr, error = %e, "Failed to add remote address");
        }

        op.commit()?;
        Ok(())
    }

    /// Track tree/peer sync relationship when a peer requests a tree.
    ///
    /// This method adds the tree to the peer's sync list, enabling bidirectional
    /// sync for the requested tree. This is critical for `sync_on_commit` to work
    /// in both directions.
    ///
    /// # Arguments
    /// * `tree_id` - The ID of the tree being requested
    /// * `peer_pubkey` - The public key of the peer requesting the tree (device key, not auth key)
    ///
    /// # Returns
    /// Result indicating success or failure
    fn track_tree_sync_relationship(&self, tree_id: &ID, peer_pubkey: &str) -> Result<()> {
        let sync_tree = self.get_sync_tree()?;
        let op = sync_tree.new_transaction()?;
        let peer_manager = PeerManager::new(&op);

        // Add the tree sync relationship
        peer_manager.add_tree_sync(peer_pubkey, tree_id)?;
        op.commit()?;

        debug!(tree_id = %tree_id, peer_pubkey = %peer_pubkey, "Tracked tree/peer sync relationship");
        Ok(())
    }

    /// Handle a handshake request from a peer.
    async fn handle_handshake(
        &self,
        request: &HandshakeRequest,
        context: &RequestContext,
    ) -> SyncResponse {
        async move {
            debug!(
                peer_device_id = %request.device_id,
                peer_public_key = %request.public_key,
                display_name = ?request.display_name,
                protocol_version = request.protocol_version,
                "Processing handshake request"
            );

            // Check protocol version compatibility
            if request.protocol_version != PROTOCOL_VERSION {
                warn!(
                    expected = PROTOCOL_VERSION,
                    received = request.protocol_version,
                    "Protocol version mismatch"
                );
                return SyncResponse::Error(format!(
                    "Protocol version mismatch: expected {}, got {}",
                    PROTOCOL_VERSION, request.protocol_version
                ));
            }

            // Get device signing key from backend
            let instance = match self.instance() {
                Ok(i) => i,
                Err(e) => {
                    error!(error = %e, "Failed to get instance");
                    return SyncResponse::Error(format!("Failed to get instance: {e}"));
                }
            };
            let signing_key = match instance.backend().get_private_key(DEVICE_KEY_NAME) {
                Ok(Some(key)) => {
                    debug!(device_key_name = %DEVICE_KEY_NAME, "Retrieved device signing key");
                    key
                }
                Ok(None) => {
                    error!(device_key_name = %DEVICE_KEY_NAME, "Device key not found");
                    return SyncResponse::Error("Device key not found".to_string());
                }
                Err(e) => {
                    error!(device_key_name = %DEVICE_KEY_NAME, error = %e, "Failed to get signing key");
                    return SyncResponse::Error(format!("Failed to get signing key: {e}"));
                }
            };

            // Generate device ID and public key from signing key
            let verifying_key = signing_key.verifying_key();
            let public_key = format_public_key(&verifying_key);
            let device_id = public_key.clone(); // Device ID is the public key

            // Sign the challenge with our device key to prove identity
            let challenge_response = create_challenge_response(&request.challenge, &signing_key);

            // Generate a new challenge for mutual authentication
            let new_challenge = generate_challenge();

            // Get available trees for discovery
            let available_trees = self.get_available_trees().await;

            // Register the peer and add their addresses to our peer list
            match self.register_incoming_peer(&request.public_key, request.display_name.as_deref(), &request.listen_addresses, &context.remote_address) {
                Ok(()) => {
                    debug!(peer_pubkey = %request.public_key, "Successfully registered incoming peer");
                }
                Err(e) => {
                    // Log the error but don't fail the handshake - peer registration is best-effort
                    warn!(peer_pubkey = %request.public_key, error = %e, "Failed to register incoming peer");
                }
            }

            info!(
                our_device_id = %device_id,
                peer_device_id = %request.device_id,
                tree_count = available_trees.len(),
                "Handshake completed successfully"
            );

            SyncResponse::Handshake(HandshakeResponse {
                device_id,
                public_key,
                display_name: Some("Eidetica Peer".to_string()),
                protocol_version: PROTOCOL_VERSION,
                challenge_response,
                new_challenge,
                available_trees,
            })
        }
        .instrument(info_span!("handle_handshake", peer = %request.device_id))
        .await
    }

    /// Handle a unified sync tree request (bootstrap or incremental).
    ///
    /// This method routes between two sync modes:
    /// 1. **Bootstrap**: When peer has no tips (empty database), sends complete tree
    /// 2. **Incremental**: When peer has existing tips, sends only new entries
    ///
    /// # Bootstrap Authentication
    /// During bootstrap, if the peer provides authentication credentials:
    /// - `requesting_key`: Public key to add
    /// - `requesting_key_name`: Name for the key
    /// - `requested_permission`: Access level requested
    ///
    /// The handler will evaluate the bootstrap policy and either:
    /// - Auto-approve and add the key immediately
    /// - Store request for manual approval
    /// - Proceed without authentication (anonymous bootstrap)
    async fn handle_sync_tree(
        &self,
        request: &SyncTreeRequest,
        context: &RequestContext,
    ) -> SyncResponse {
        async move {
            trace!(tree_id = %request.tree_id, "Processing sync tree request");

            // Track tree/peer sync relationship for bidirectional sync
            // IMPORTANT: Only use context.peer_pubkey (device key from handshake)
            // Do NOT use request.requesting_key (that's an auth key for database access)
            if let Some(peer_pubkey) = &context.peer_pubkey {
                if let Err(e) = self.track_tree_sync_relationship(&request.tree_id, peer_pubkey) {
                    // Log the error but don't fail the sync - relationship tracking is best-effort
                    warn!(tree_id = %request.tree_id, peer_pubkey = %peer_pubkey, error = %e, "Failed to track tree/peer relationship");
                }
            } else {
                debug!(tree_id = %request.tree_id, "No peer pubkey in context, skipping relationship tracking");
            }

            // Check if peer needs bootstrap (empty tips indicates no local data)
            if request.our_tips.is_empty() {
                debug!(tree_id = %request.tree_id, "Peer needs bootstrap - sending full tree");
                return self.handle_bootstrap_request(&request.tree_id,
                                                  request.requesting_key.as_deref(),
                                                  request.requesting_key_name.as_deref(),
                                                  request.requested_permission.clone()).await;
            }

            // Handle incremental sync (peer has existing data, needs updates)
            debug!(tree_id = %request.tree_id, peer_tips = request.our_tips.len(), "Handling incremental sync");
            self.handle_incremental_sync(&request.tree_id, &request.our_tips).await
        }
        .instrument(info_span!("handle_sync_tree", tree = %request.tree_id))
        .await
    }

    /// Handle bootstrap request by sending complete tree state and optionally approving auth key.
    ///
    /// Bootstrap is the initial synchronization when a peer has no local data for a tree.
    /// This method:
    /// 1. Validates the tree exists and sync is enabled
    /// 2. Processes authentication and permission resolution
    /// 3. Sends all entries from the tree to the peer
    ///
    /// # Authentication Flow
    ///
    /// The bootstrap process handles three authentication scenarios:
    ///
    /// ## 1. Explicit Permission Request
    /// When all three auth parameters are provided (`requesting_key`, `requesting_key_name`, `requested_permission`):
    /// - Check if key already has sufficient permissions
    /// - If yes: Approve immediately without adding key
    /// - If no: Store request for manual approval and return `BootstrapPending`
    ///
    /// ## 2. Auto-Detection
    /// When key is provided but `requested_permission` is `None`:
    /// - Look up key's existing permissions in database auth settings
    /// - Uses `find_all_sigkeys_for_pubkey()` to find all permissions (direct + global wildcard)
    /// - If key found: Use highest available permission and approve immediately
    /// - If key not found: Reject with authentication error
    ///
    /// ## 3. Unauthenticated Access
    /// When no `requesting_key` is provided:
    /// - Only allowed if database has no auth configured or has global wildcard permission
    /// - Otherwise rejected with authentication required error
    ///
    /// # SECURITY WARNING
    ///
    /// **FIXME: This function does not verify that the peer actually controls the `requesting_key`.**
    ///
    /// The `requesting_key` parameter is an unverified string from the client. This function
    /// does not receive `RequestContext` (which contains the verified `peer_pubkey` from handshake),
    /// so it cannot verify the peer actually owns the key they claim.
    ///
    /// # Arguments
    /// * `tree_id` - The database/tree to bootstrap
    /// * `requesting_key` - Optional public key requesting access (UNVERIFIED!)
    /// * `requesting_key_name` - Optional name/identifier for the key (UNVERIFIED!)
    /// * `requested_permission` - Optional permission level requested (if None, auto-detects from auth settings)
    ///
    /// # Returns
    /// - `BootstrapResponse`: Contains entries and approval status (key_approved, granted_permission)
    /// - `BootstrapPending`: Manual approval required (request queued)
    /// - `Error`: Tree not found, auth required, key not authorized, or processing failure
    async fn handle_bootstrap_request(
        &self,
        tree_id: &crate::entry::ID,
        requesting_key: Option<&str>,
        requesting_key_name: Option<&str>,
        requested_permission: Option<crate::auth::Permission>,
    ) -> SyncResponse {
        // SECURITY: Check if database has sync enabled (FIRST CHECK - before anything else)
        // This prevents information leakage about database existence
        if !self.is_database_sync_enabled(tree_id).await {
            warn!(
                tree_id = %tree_id,
                "Sync request for non-sync-enabled database - rejecting as not found"
            );
            return SyncResponse::Error(format!("Tree not found: {tree_id}"));
        }

        // Get the root entry (to verify tree exists)
        let instance = match self.instance() {
            Ok(i) => i,
            Err(e) => return SyncResponse::Error(format!("Instance dropped: {e}")),
        };
        let _root_entry = match instance.backend().get(tree_id) {
            Ok(entry) => entry,
            Err(e) if e.is_not_found() => {
                warn!(tree_id = %tree_id, "Tree not found for bootstrap");
                return SyncResponse::Error(format!("Tree not found: {tree_id}"));
            }
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to get root entry");
                return SyncResponse::Error(format!("Failed to get tree root: {e}"));
            }
        };

        // Check if database has authentication configured
        let auth_configured = match self.check_if_database_has_auth(tree_id).await {
            Ok(has_auth) => has_auth,
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to check if database has auth");
                return SyncResponse::Error(format!("Failed to check database auth: {e}"));
            }
        };

        // If auth is configured but no credentials provided, reject the request
        if auth_configured && requesting_key.is_none() {
            warn!(
                tree_id = %tree_id,
                "Unauthenticated bootstrap request rejected - database requires authentication"
            );
            return SyncResponse::Error(
                "Authentication required: This database requires authenticated access. \
                 Please provide credentials (requesting_key, requesting_key_name, requested_permission) \
                 to bootstrap sync.".to_string()
            );
        }

        // Handle key approval for bootstrap requests FIRST
        let (key_approved, granted_permission) = match (
            requesting_key,
            requesting_key_name,
            requested_permission,
        ) {
            // Case 1: All three parameters provided - explicit permission request
            (Some(key), Some(key_name), Some(permission)) => {
                info!(
                    tree_id = %tree_id,
                    requesting_key = %key,
                    key_name = %key_name,
                    requested_permission = ?permission,
                    "Processing key approval request for bootstrap"
                );

                // Check if the requesting key already has sufficient permissions through existing auth
                match self
                    .check_existing_auth_permission(tree_id, key, &permission)
                    .await
                {
                    Ok(true) => {
                        // Key already has sufficient permission - approve without adding
                        info!(
                            tree_id = %tree_id,
                            key = %key,
                            permission = ?permission,
                            "Bootstrap approved via existing auth permission - no key added"
                        );
                        (true, Some(permission))
                    }
                    Ok(false) => {
                        // No existing permission, store request for manual approval
                        info!(tree_id = %tree_id, "Bootstrap key approval requested - storing for manual approval");

                        // Store the bootstrap request in sync database for manual approval
                        match self
                            .store_bootstrap_request(tree_id, key, key_name, &permission)
                            .await
                        {
                            Ok(request_id) => {
                                info!(
                                    tree_id = %tree_id,
                                    request_id = %request_id,
                                    "Bootstrap request stored for manual approval"
                                );
                                return SyncResponse::BootstrapPending {
                                    request_id,
                                    message: "Bootstrap request pending manual approval"
                                        .to_string(),
                                };
                            }
                            Err(e) => {
                                error!(
                                    tree_id = %tree_id,
                                    error = %e,
                                    "Failed to store bootstrap request"
                                );
                                return SyncResponse::Error(format!(
                                    "Failed to store bootstrap request: {e}"
                                ));
                            }
                        }
                    }
                    Err(e) => {
                        error!(tree_id = %tree_id, error = %e, "Failed to check global permission for bootstrap");
                        return SyncResponse::Error(format!("Global permission check failed: {e}"));
                    }
                }
            }

            // Case 2: Key provided but permission not specified - auto-detect from auth settings
            (Some(key), Some(_key_name), None) => {
                info!(
                    tree_id = %tree_id,
                    requesting_key = %key,
                    "Auto-detecting permission from auth settings for bootstrap request"
                );

                match self.get_key_highest_permission(tree_id, key).await {
                    Ok(Some(permission)) => {
                        info!(
                            tree_id = %tree_id,
                            requesting_key = %key,
                            detected_permission = ?permission,
                            "Approved bootstrap using auto-detected permission from auth settings"
                        );
                        (true, Some(permission))
                    }
                    Ok(None) => {
                        warn!(
                            tree_id = %tree_id,
                            requesting_key = %key,
                            "Key not found in auth settings - rejecting bootstrap request"
                        );
                        return SyncResponse::Error(
                            "Authentication required: provided key is not authorized for this database".to_string()
                        );
                    }
                    Err(e) => {
                        error!(
                            tree_id = %tree_id,
                            requesting_key = %key,
                            error = %e,
                            "Failed to lookup key permissions"
                        );
                        return SyncResponse::Error(format!("Failed to access auth settings: {e}"));
                    }
                }
            }

            // Case 3: No key provided, or key provided without key_name - unauthenticated access
            _ => {
                debug!(
                    tree_id = %tree_id,
                    "No authentication credentials provided - proceeding with unauthenticated bootstrap"
                );
                (false, None)
            }
        };

        // NOW collect all entries after key approval (so we get the updated database state)
        let all_entries = match self.collect_all_entries_for_bootstrap(tree_id).await {
            Ok(entries) => entries,
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to collect all entries for bootstrap after key approval");
                return SyncResponse::Error(format!(
                    "Failed to collect all entries for bootstrap: {e}"
                ));
            }
        };

        // For bootstrap, we need to send the actual root entry (tree_id) as root_entry
        // The root_entry should always be the tree's root, not a tip
        let instance = match self.instance() {
            Ok(i) => i,
            Err(e) => return SyncResponse::Error(format!("Instance dropped: {e}")),
        };
        let root_entry = match instance.backend().get(tree_id) {
            Ok(entry) => entry,
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to get root entry");
                return SyncResponse::Error(format!("Failed to get root entry: {e}"));
            }
        };

        // Filter out the root from all_entries since we send it separately as root_entry
        let other_entries: Vec<_> = all_entries
            .into_iter()
            .filter(|entry| entry.id() != tree_id)
            .collect();

        info!(
            tree_id = %tree_id,
            entry_count = other_entries.len() + 1,
            key_approved = key_approved,
            "Sending bootstrap response"
        );

        SyncResponse::Bootstrap(BootstrapResponse {
            tree_id: tree_id.clone(),
            root_entry,
            all_entries: other_entries,
            key_approved,
            granted_permission,
        })
    }

    /// Handle incremental sync request
    async fn handle_incremental_sync(
        &self,
        tree_id: &crate::entry::ID,
        peer_tips: &[crate::entry::ID],
    ) -> SyncResponse {
        // SECURITY: Check if database has sync enabled (FIRST CHECK - before anything else)
        // This prevents information leakage about database existence
        if !self.is_database_sync_enabled(tree_id).await {
            warn!(
                tree_id = %tree_id,
                "Incremental sync request for non-sync-enabled database - rejecting as not found"
            );
            return SyncResponse::Error(format!("Tree not found: {tree_id}"));
        }

        // Get our current tips
        let instance = match self.instance() {
            Ok(i) => i,
            Err(e) => return SyncResponse::Error(format!("Instance dropped: {e}")),
        };
        let our_tips = match instance.backend().get_tips(tree_id) {
            Ok(tips) => tips,
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to get our tips");
                return SyncResponse::Error(format!("Failed to get tips: {e}"));
            }
        };

        // Find entries peer is missing
        let missing_entries = match self.find_missing_entries_for_peer(&our_tips, peer_tips) {
            Ok(entries) => entries,
            Err(e) => {
                error!(tree_id = %tree_id, error = %e, "Failed to find missing entries");
                return SyncResponse::Error(format!("Failed to find missing entries: {e}"));
            }
        };

        debug!(
            tree_id = %tree_id,
            our_tips = our_tips.len(),
            peer_tips = peer_tips.len(),
            missing_count = missing_entries.len(),
            "Sending incremental sync response"
        );

        SyncResponse::Incremental(IncrementalResponse {
            tree_id: tree_id.clone(),
            their_tips: our_tips,
            missing_entries,
        })
    }

    /// Get list of available trees for discovery
    async fn get_available_trees(&self) -> Vec<TreeInfo> {
        // Get all root entries in the backend
        let instance = match self.instance() {
            Ok(i) => i,
            Err(e) => {
                error!(error = %e, "Failed to get instance");
                return Vec::new();
            }
        };
        match instance.backend().all_roots() {
            Ok(roots) => {
                let mut tree_infos = Vec::new();
                for root_id in roots {
                    // Get basic tree info
                    if let Ok(entry_count) = self.count_tree_entries(&root_id) {
                        tree_infos.push(TreeInfo {
                            tree_id: root_id,
                            name: None, // Could extract from tree metadata in the future
                            entry_count,
                            last_modified: 0, // Could track modification times in the future
                        });
                    }
                }
                tree_infos
            }
            Err(e) => {
                error!(error = %e, "Failed to get available trees");
                Vec::new()
            }
        }
    }

    /// Collect all entries in a tree (excluding the root)
    #[allow(dead_code)]
    async fn collect_all_tree_entries(
        &self,
        tree_id: &crate::entry::ID,
    ) -> crate::Result<Vec<crate::entry::Entry>> {
        let mut entries = Vec::new();
        let mut visited = std::collections::HashSet::new();
        let mut to_visit = std::collections::VecDeque::new();

        // Get tips to start traversal
        let tips = self.instance()?.backend().get_tips(tree_id)?;
        to_visit.extend(tips);

        // Traverse the DAG depth-first
        while let Some(entry_id) = to_visit.pop_front() {
            if visited.contains(&entry_id) || entry_id == *tree_id {
                continue; // Skip root and already visited
            }
            visited.insert(entry_id.clone());

            match self.instance()?.backend().get(&entry_id) {
                Ok(entry) => {
                    // Add parents to visit list
                    if let Ok(parent_ids) = entry.parents() {
                        for parent_id in parent_ids {
                            if !visited.contains(&parent_id) && parent_id != *tree_id {
                                to_visit.push_back(parent_id);
                            }
                        }
                    }
                    entries.push(entry);
                }
                Err(e) if e.is_not_found() => {
                    warn!(entry_id = %entry_id, "Entry not found during traversal");
                }
                Err(e) => {
                    error!(entry_id = %entry_id, error = %e, "Error during traversal");
                    return Err(e);
                }
            }
        }

        Ok(entries)
    }

    /// Collect ALL entries in a tree for bootstrap (including root)
    async fn collect_all_entries_for_bootstrap(
        &self,
        tree_id: &crate::entry::ID,
    ) -> crate::Result<Vec<crate::entry::Entry>> {
        let mut entries = Vec::new();
        let mut visited = std::collections::HashSet::new();
        let mut to_visit = std::collections::VecDeque::new();

        // Get tips to start traversal
        let tips = self.instance()?.backend().get_tips(tree_id)?;
        to_visit.extend(tips);

        // Traverse the DAG depth-first, INCLUDING the root
        while let Some(entry_id) = to_visit.pop_front() {
            if visited.contains(&entry_id) {
                continue; // Skip already visited (but don't skip root)
            }
            visited.insert(entry_id.clone());

            match self.instance()?.backend().get(&entry_id) {
                Ok(entry) => {
                    // Add parents to visit list
                    if let Ok(parent_ids) = entry.parents() {
                        for parent_id in parent_ids {
                            if !visited.contains(&parent_id) {
                                to_visit.push_back(parent_id);
                            }
                        }
                    }
                    entries.push(entry);
                }
                Err(e) if e.is_not_found() => {
                    warn!(entry_id = %entry_id, "Entry not found during traversal");
                }
                Err(e) => {
                    error!(entry_id = %entry_id, error = %e, "Error during traversal");
                    return Err(e);
                }
            }
        }

        // IMPORTANT: Reverse the entries so parents come before children
        // The traversal collects children first (starting from tips), but we need
        // to store parents first for proper tip tracking
        entries.reverse();

        Ok(entries)
    }

    /// Find entries that peer is missing
    fn find_missing_entries_for_peer(
        &self,
        our_tips: &[crate::entry::ID],
        peer_tips: &[crate::entry::ID],
    ) -> crate::Result<Vec<crate::entry::Entry>> {
        // Find tips they don't have
        let missing_tip_ids: Vec<_> = our_tips
            .iter()
            .filter(|tip_id| !peer_tips.contains(tip_id))
            .cloned()
            .collect();

        if missing_tip_ids.is_empty() {
            return Ok(Vec::new());
        }

        // Collect ancestors
        super::utils::collect_ancestors_to_send(
            self.instance()?.backend().as_backend_impl(),
            &missing_tip_ids,
            peer_tips,
        )
    }

    /// Count entries in a tree
    fn count_tree_entries(&self, tree_id: &crate::entry::ID) -> crate::Result<usize> {
        let mut count = 1; // Include root
        let mut visited = std::collections::HashSet::new();
        let mut to_visit = std::collections::VecDeque::new();

        // Get tips to start traversal
        let tips = self.instance()?.backend().get_tips(tree_id)?;
        to_visit.extend(tips);

        // Count all entries
        while let Some(entry_id) = to_visit.pop_front() {
            if visited.contains(&entry_id) || entry_id == *tree_id {
                continue;
            }
            visited.insert(entry_id.clone());
            count += 1;

            if let Ok(entry) = self.instance()?.backend().get(&entry_id)
                && let Ok(parent_ids) = entry.parents()
            {
                for parent_id in parent_ids {
                    if !visited.contains(&parent_id) && parent_id != *tree_id {
                        to_visit.push_back(parent_id);
                    }
                }
            }
        }

        Ok(count)
    }
}