Skip to main content

coven_core/sync/
store_pull.rs

1//! Causal discovery and atomic materialization for immutable Store commits.
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::sync::Arc;
5
6use super::apply::{
7    apply_changeset_strict_on, resolve_and_apply_changeset_with_schema_on, ValidatedChangeset,
8};
9use super::audience_package::{AudiencePackage, PackageAudience};
10use super::circle_control::StoreMembershipStateRef;
11use super::conflict::TableSchema;
12use super::membership::{MembershipChain, MembershipStatus, SerialAuthorizationState};
13use super::pull::{
14    advance_max_updated_at, cache_eager_blobs, download_blobs, local_blob_cleanup_intents,
15};
16use super::session::SyncedTable;
17use super::storage::{
18    CoordinationError, CoordinationStorage, ProtocolObjectContext, ProtocolObjectDomain,
19    StorageError, SyncStorage,
20};
21use super::store_commit::{
22    head_slot_prefix, serial_head_key, ActivatedStoreDeviceRegistrationRef, CommitFrontier,
23    DeviceJoinAttempt, DeviceJoinOutcomeBody, DeviceStreamAnchor, ObjectHash, OwnerRecoveryNode,
24    OwnerRecoveryNodeRef, ResolvedStoreDeviceState, StoreBatchCommit, StoreBatchCommitRef,
25    StoreCommitAnchor, StoreCommitCoord, StoreDeviceHead, StoreDeviceRegistration,
26    StoreDeviceRegistrationActivation, StoreDeviceRegistrationActivationRef,
27    StoreDeviceRegistrationOrigin, StoreDeviceRegistrationRef, StoreDeviceStateRef,
28    StoreHistoryCut, StoreProtocolError, StoreRootRef, StoreSerialHead, StoreSerialHeadState,
29    StoreSerialPredecessor, StreamActivationId, SERIAL_STREAM_ID,
30};
31use super::store_objects::{
32    load_commit_ref, load_device_join_attempt_ref, load_device_join_outcome_ref,
33    load_founder_registration, load_owner_recovery_node_ref, load_registration_ref,
34    load_store_ack_ref, load_store_package, load_store_protocol_root, StoreObjectError,
35};
36use crate::blob::local_cleanup::{self, LocalBlobCleanupIntent};
37use crate::changeset::RowChange;
38use crate::database::{BlobActivation, Database, DbError};
39use crate::store_dir::StoreDir;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum HeldStorePositionReason {
43    MissingCommit,
44    MissingPackage,
45    MissingDeviceRegistration {
46        device_id: String,
47        revision: u64,
48        registration_hash: ObjectHash,
49    },
50    MissingPredecessor(StoreBatchCommitRef),
51    MissingDependency {
52        device_id: String,
53        commit: StoreBatchCommitRef,
54    },
55    NewerSchema {
56        local: u32,
57        required: u32,
58    },
59    Unauthorized,
60    InvalidChangeset(String),
61    InvalidRowIdentity {
62        table: String,
63        reason: String,
64    },
65    BlobDownloadFailed,
66    ForeignKeyDependency,
67    ConstraintConflict(Vec<String>),
68    HashMismatch {
69        referenced_device_id: String,
70        referenced_commit: StoreBatchCommitRef,
71        materialized_hash: ObjectHash,
72    },
73    InvalidSignature,
74    WrongSlot(String),
75    ObjectCollision(String),
76    ObjectUnreadable {
77        key: String,
78        detail: String,
79    },
80    InvalidObject(String),
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum HeldStoreCoordinate {
85    Head {
86        device_id: String,
87        seq: u64,
88        head_hash: ObjectHash,
89    },
90    Commit {
91        device_id: String,
92        commit: StoreBatchCommitRef,
93    },
94    Package {
95        device_id: String,
96        seq: u64,
97        package_hash: ObjectHash,
98    },
99    Dependency {
100        dependent_device_id: String,
101        dependent_commit: StoreBatchCommitRef,
102        required_device_id: String,
103        required_commit: StoreBatchCommitRef,
104    },
105}
106
107impl HeldStoreCoordinate {
108    pub fn device_id(&self) -> &str {
109        match self {
110            Self::Head { device_id, .. }
111            | Self::Commit { device_id, .. }
112            | Self::Package { device_id, .. } => device_id,
113            Self::Dependency {
114                dependent_device_id,
115                ..
116            } => dependent_device_id,
117        }
118    }
119
120    pub fn seq(&self) -> u64 {
121        match self {
122            Self::Head { seq, .. } | Self::Package { seq, .. } => *seq,
123            Self::Commit { commit, .. } => commit.coord.sequence(),
124            Self::Dependency {
125                dependent_commit, ..
126            } => dependent_commit.coord.sequence(),
127        }
128    }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct HeldStorePosition {
133    pub coordinate: HeldStoreCoordinate,
134    pub reason: HeldStorePositionReason,
135}
136
137#[derive(Debug)]
138pub struct StorePullResult {
139    pub changesets_applied: u64,
140    pub devices_pulled: u64,
141    pub held_positions: Vec<HeldStorePosition>,
142    pub visible_heads: Vec<VerifiedStoreDeviceHead>,
143    pub serial_head: Option<StoreSerialHead>,
144    pub row_changes: Vec<RowChange>,
145    pub asset_downloads_failed: bool,
146    pub local_blob_cleanup_pending: bool,
147    pub frontier: BTreeMap<String, StoreBatchCommitRef>,
148}
149
150#[derive(Debug, Clone)]
151pub struct VerifiedStoreDeviceHead {
152    pub head: StoreDeviceHead,
153    pub author: StoreDeviceRegistration,
154}
155
156#[derive(Debug, thiserror::Error)]
157pub enum StorePullError {
158    #[error("{0}")]
159    Object(#[from] StoreObjectError),
160    #[error("database: {0}")]
161    Database(String),
162    #[error("membership: {0}")]
163    Membership(#[source] StorePullMembershipError),
164    #[error("Serial Store: {0}")]
165    Serial(String),
166    #[error("Serial coordination: {0}")]
167    Coordination(#[source] CoordinationError),
168    #[error("{0}")]
169    BlobDownloads(#[source] super::pull::BlobDownloadFailures),
170}
171
172#[derive(Debug, thiserror::Error)]
173pub enum StorePullMembershipError {
174    #[error("{0}")]
175    Object(#[source] StoreObjectError),
176    #[error("{0}")]
177    Chain(#[source] super::membership_ops::AnchoredChainError),
178    #[error("{0}")]
179    Message(String),
180}
181
182impl From<DbError> for StorePullError {
183    fn from(error: DbError) -> Self {
184        Self::Database(error.into_message())
185    }
186}
187
188#[derive(Clone)]
189struct Candidate {
190    commit_ref: StoreBatchCommitRef,
191    commit: StoreBatchCommit,
192    author: StoreDeviceRegistration,
193    package: Option<Vec<u8>>,
194    registrations: Vec<(StoreDeviceRegistration, StoreDeviceRegistrationActivation)>,
195    circle_activations: Vec<super::circle_ops::VerifiedCircleReference>,
196}
197
198fn parse_candidate_store_package(
199    candidate: &Candidate,
200    bytes: &[u8],
201) -> Result<AudiencePackage, String> {
202    let package = AudiencePackage::parse(bytes)
203        .map_err(|error| format!("invalid Store audience package: {error}"))?;
204    if !matches!(package.audience(), PackageAudience::Store)
205        || package.store_root_hash() != candidate.commit.store_root_hash
206        || package.write_id() != &candidate.commit.write_id
207        || package.commit_coord() != &candidate.commit_ref.coord
208        || package.candidate_family() != candidate.commit.candidate_family()
209        || candidate
210            .commit
211            .store_package()
212            .as_ref()
213            .is_none_or(|reference| package.schema_version() != reference.schema_version)
214    {
215        return Err("Store audience package differs from its exact commit".to_string());
216    }
217    Ok(package)
218}
219
220struct AuthorizedSerialCommit {
221    commit_ref: StoreBatchCommitRef,
222    commit: StoreBatchCommit,
223    author: StoreDeviceRegistration,
224    registrations: Vec<(StoreDeviceRegistration, StoreDeviceRegistrationActivation)>,
225    authorization_after: SerialAuthorizationState,
226}
227
228enum RegistrationLoadError {
229    Object(StoreObjectError),
230    Invalid(String),
231}
232
233enum RegistrationPredecessorAuthority<'a> {
234    MergeConcurrent(&'a MembershipChain),
235    Serial {
236        authorization: &'a SerialAuthorizationState,
237        position: super::store_commit::SerialStorePosition,
238    },
239}
240
241impl RegistrationPredecessorAuthority<'_> {
242    fn verifies_owner(
243        &self,
244        membership: &StoreMembershipStateRef,
245        owner_pubkey: &str,
246        owner_grant: &super::membership::MembershipGrantId,
247    ) -> bool {
248        match self {
249            Self::MergeConcurrent(chain) => {
250                let MembershipStatus::Resolved(resolved) = chain.status() else {
251                    return false;
252                };
253                StoreMembershipStateRef::merge_concurrent(
254                    chain.head_refs().to_vec(),
255                    chain.resolution_refs().to_vec(),
256                    membership.recovery().to_vec(),
257                    resolved.state_hash,
258                )
259                .is_ok_and(|expected| membership == &expected)
260                    && chain.active_owner_grant(owner_pubkey).as_ref() == Some(owner_grant)
261            }
262            Self::Serial {
263                authorization,
264                position,
265            } => {
266                StoreMembershipStateRef::serial(
267                    position.clone(),
268                    membership.recovery().to_vec(),
269                    authorization,
270                )
271                .is_ok_and(|expected| membership == &expected)
272                    && authorization
273                        .membership
274                        .authorizes_owner_grant_id(owner_pubkey, owner_grant)
275            }
276        }
277    }
278
279    fn verifies_active_owner(&self, owner_pubkey: &str) -> bool {
280        match self {
281            Self::MergeConcurrent(chain) => chain.is_owner_now(owner_pubkey),
282            Self::Serial { authorization, .. } => authorization.membership.is_owner(owner_pubkey),
283        }
284    }
285
286    fn verifies_provider_administrator(
287        &self,
288        grant_id: &super::provider::ProviderAdminGrantId,
289        executor: &StoreDeviceRegistrationRef,
290        expected: &super::provider::ProviderAdminGrantRecord,
291    ) -> bool {
292        let state = match self {
293            Self::MergeConcurrent(chain) => {
294                let super::membership::MembershipStatus::Resolved(resolved) = chain.status() else {
295                    return false;
296                };
297                resolved.provider_admin.combined_state()
298            }
299            Self::Serial { authorization, .. } => &authorization.provider_admin,
300        };
301        state.authorizes(grant_id, executor)
302            && state
303                .records()
304                .get(grant_id)
305                .is_some_and(|record| record == expected)
306    }
307}
308
309async fn load_merge_predecessor_membership(
310    storage: &dyn SyncStorage,
311    root: &StoreRootRef,
312    state: &StoreMembershipStateRef,
313) -> Result<MembershipChain, RegistrationLoadError> {
314    let StoreMembershipStateRef::MergeConcurrent {
315        heads, resolutions, ..
316    } = state
317    else {
318        return Err(RegistrationLoadError::Invalid(
319            "Merge registration lifecycle commit carries Serial membership state".to_string(),
320        ));
321    };
322    let root_value = Box::pin(load_store_protocol_root(storage, root))
323        .await
324        .map_err(RegistrationLoadError::Object)?
325        .value;
326    Box::pin(super::membership_ops::load_anchored_chain_at_exact_heads(
327        storage,
328        root,
329        &root_value.descriptor.founder_pubkey,
330        heads,
331        resolutions,
332    ))
333    .await
334    .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))
335}
336
337pub(crate) enum DeviceJoinBootstrapAuthorization {
338    MergeConcurrent {
339        state: StoreMembershipStateRef,
340        chain: MembershipChain,
341    },
342    Serial {
343        state: StoreMembershipStateRef,
344        position: super::store_commit::SerialStorePosition,
345        authorization: SerialAuthorizationState,
346    },
347}
348
349pub(crate) async fn load_device_join_authorization(
350    storage: &dyn SyncStorage,
351    root: &StoreRootRef,
352    state: &StoreMembershipStateRef,
353) -> Result<DeviceJoinBootstrapAuthorization, StorePullError> {
354    match state {
355        StoreMembershipStateRef::MergeConcurrent { .. } => {
356            let chain = load_merge_predecessor_membership(storage, root, state)
357                .await
358                .map_err(|error| match error {
359                    RegistrationLoadError::Object(error) => StorePullError::Object(error),
360                    RegistrationLoadError::Invalid(error) => StorePullError::Database(error),
361                })?;
362            Ok(DeviceJoinBootstrapAuthorization::MergeConcurrent {
363                state: state.clone(),
364                chain,
365            })
366        }
367        StoreMembershipStateRef::Serial {
368            position, recovery, ..
369        } => {
370            let reference = match position {
371                super::store_commit::SerialStorePosition::Genesis { .. } => None,
372                super::store_commit::SerialStorePosition::Commit(reference) => {
373                    Some(reference.clone())
374                }
375            };
376            let authorization =
377                load_serial_authorization_at_position(storage, root, reference).await?;
378            let expected =
379                StoreMembershipStateRef::serial(position.clone(), recovery.clone(), &authorization)
380                    .map_err(|error| StorePullError::Database(error.to_string()))?;
381            if &expected != state {
382                return Err(StorePullError::Database(
383                    "Serial device join membership state differs from its exact authorization"
384                        .to_string(),
385                ));
386            }
387            Ok(DeviceJoinBootstrapAuthorization::Serial {
388                state: expected,
389                position: position.clone(),
390                authorization,
391            })
392        }
393    }
394}
395
396async fn load_commit_registrations(
397    storage: &dyn SyncStorage,
398    root: &StoreRootRef,
399    commit: &StoreBatchCommit,
400    activating_author: &StoreDeviceRegistration,
401    predecessor: Option<&RegistrationPredecessorAuthority<'_>>,
402) -> Result<Vec<(StoreDeviceRegistration, StoreDeviceRegistrationActivation)>, RegistrationLoadError>
403{
404    Box::pin(validate_commit_join_attempts(
405        storage,
406        root,
407        commit,
408        activating_author,
409        predecessor,
410    ))
411    .await?;
412    Box::pin(validate_commit_join_outcomes(
413        storage,
414        root,
415        commit,
416        activating_author,
417        predecessor,
418    ))
419    .await?;
420    Box::pin(validate_commit_join_abandonments(
421        storage,
422        root,
423        commit,
424        activating_author,
425        predecessor,
426    ))
427    .await?;
428    Box::pin(validate_commit_join_cleanup_receipts(
429        storage,
430        root,
431        commit,
432        activating_author,
433        predecessor,
434    ))
435    .await?;
436    let mut registrations = Vec::with_capacity(commit.device_registrations().len());
437    for activated in commit.device_registrations() {
438        let registration = load_registration_ref(storage, root, &activated.registration)
439            .await
440            .map_err(RegistrationLoadError::Object)?
441            .value;
442        let predecessor = predecessor.ok_or_else(|| {
443            RegistrationLoadError::Invalid(
444                "registration activation has no exact predecessor membership authority".to_string(),
445            )
446        })?;
447        let authority = registration_activation(
448            storage,
449            root,
450            activated,
451            &registration,
452            activating_author,
453            &commit.order,
454            commit.serial_recovery_activation(),
455            predecessor,
456        )
457        .await?;
458        registrations.push((registration, authority));
459    }
460    for retirement in commit.device_retirements() {
461        if retirement.target != commit.author_registration {
462            return Err(RegistrationLoadError::Invalid(
463                "self-retirement targets a different exact registration".to_string(),
464            ));
465        }
466        let context = ProtocolObjectContext::store(
467            root.store_root_hash,
468            ProtocolObjectDomain::StoreDeviceSelfRetirement,
469        );
470        let bytes = storage
471            .read_protocol_object(
472                &context,
473                &retirement.object,
474                &super::store_commit::device_self_retirement_semantic_prefix(
475                    commit.candidate_family(),
476                    &retirement.target.device_id,
477                    retirement.retirement_hash,
478                ),
479            )
480            .await
481            .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
482        super::store_commit::StoreDeviceSelfRetirement::parse_at(
483            &bytes,
484            retirement,
485            activating_author,
486        )
487        .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
488    }
489    Ok(registrations)
490}
491
492async fn validate_commit_join_abandonments(
493    storage: &dyn SyncStorage,
494    root: &StoreRootRef,
495    commit: &StoreBatchCommit,
496    activating_author: &StoreDeviceRegistration,
497    predecessor: Option<&RegistrationPredecessorAuthority<'_>>,
498) -> Result<(), RegistrationLoadError> {
499    if commit.device_join_abandonments().is_empty() {
500        return Ok(());
501    }
502    let predecessor = predecessor.ok_or_else(|| {
503        RegistrationLoadError::Invalid(
504            "device join abandonment activation has no exact predecessor authority".to_string(),
505        )
506    })?;
507    if !predecessor.verifies_active_owner(&activating_author.author_pubkey) {
508        return Err(RegistrationLoadError::Invalid(
509            "device join abandonment activation author is not an active Owner".to_string(),
510        ));
511    }
512    for reference in commit.device_join_abandonments() {
513        if commit
514            .device_join_attempts()
515            .iter()
516            .any(|attempt| attempt.attempt_id == reference.attempt_id)
517        {
518            return Err(RegistrationLoadError::Invalid(
519                "device join abandonment and attempt are activated together".to_string(),
520            ));
521        }
522        let context = ProtocolObjectContext::store(
523            root.store_root_hash,
524            ProtocolObjectDomain::DeviceJoinAbandonment,
525        );
526        let bytes = storage
527            .read_protocol_object(
528                &context,
529                &reference.object,
530                &super::store_commit::device_join_abandonment_semantic_prefix(reference.attempt_id),
531            )
532            .await
533            .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
534        let abandonment: super::device_join::DeviceJoinAbandonmentObject =
535            serde_json::from_slice(&bytes)
536                .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
537        if abandonment.store_root_hash != root.store_root_hash
538            || abandonment.owner_registration != commit.author_registration
539            || abandonment.attempt_slot != *reference.object.slot()
540        {
541            return Err(RegistrationLoadError::Invalid(
542                "device join abandonment differs from its activating commit".to_string(),
543            ));
544        }
545        reference
546            .verify(&abandonment, activating_author)
547            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
548    }
549    Ok(())
550}
551
552async fn validate_commit_join_cleanup_receipts(
553    storage: &dyn SyncStorage,
554    root: &StoreRootRef,
555    commit: &StoreBatchCommit,
556    activating_author: &StoreDeviceRegistration,
557    predecessor: Option<&RegistrationPredecessorAuthority<'_>>,
558) -> Result<(), RegistrationLoadError> {
559    if commit.device_join_cleanup_receipts().is_empty() {
560        return Ok(());
561    }
562    let predecessor = predecessor.ok_or_else(|| {
563        RegistrationLoadError::Invalid(
564            "device join cleanup activation has no exact predecessor authority".to_string(),
565        )
566    })?;
567    if !predecessor.verifies_active_owner(&activating_author.author_pubkey) {
568        return Err(RegistrationLoadError::Invalid(
569            "device join cleanup activation author is not an active Owner".to_string(),
570        ));
571    }
572    for reference in commit.device_join_cleanup_receipts() {
573        let context = ProtocolObjectContext::store(
574            root.store_root_hash,
575            ProtocolObjectDomain::DeviceJoinCleanupReceipt,
576        );
577        let bytes = storage
578            .read_protocol_object(
579                &context,
580                &reference.object,
581                &super::store_commit::device_join_cleanup_receipt_semantic_prefix(
582                    reference.attempt_id,
583                ),
584            )
585            .await
586            .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
587        let receipt: super::device_join::DeviceJoinCleanupReceiptObject =
588            serde_json::from_slice(&bytes)
589                .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
590        if receipt.executor != commit.author_registration
591            || receipt.membership != commit.membership_state
592            || !predecessor_contains_join_outcome(
593                storage,
594                root,
595                &commit.order,
596                &receipt.cancellation,
597            )
598            .await?
599        {
600            return Err(RegistrationLoadError::Invalid(
601                "device join cleanup receipt differs from its activating predecessor".to_string(),
602            ));
603        }
604        let attempt_ref = receipt.cancellation.attempt();
605        let attempt_context = ProtocolObjectContext::store(
606            root.store_root_hash,
607            ProtocolObjectDomain::DeviceJoinAttempt,
608        );
609        let attempt_bytes = storage
610            .read_protocol_object(
611                &attempt_context,
612                &attempt_ref.object,
613                &super::store_commit::device_join_attempt_semantic_prefix(attempt_ref.attempt_id),
614            )
615            .await
616            .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
617        let unverified: DeviceJoinAttempt = serde_json::from_slice(&attempt_bytes)
618            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
619        let owner = load_registration_ref(storage, root, &unverified.owner_registration)
620            .await
621            .map_err(RegistrationLoadError::Object)?
622            .value;
623        let attempt = DeviceJoinAttempt::parse_at(&attempt_bytes, attempt_ref, &owner)
624            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
625        let expected_administrator = &attempt.provider_approval.request.offer.provider_admin;
626        let protocol_root = load_store_protocol_root(storage, root)
627            .await
628            .map_err(RegistrationLoadError::Object)?
629            .value;
630        if !predecessor.verifies_provider_administrator(
631            &receipt.provider_admin_grant,
632            &receipt.executor,
633            expected_administrator,
634        ) || activating_author.provider != expected_administrator.provider
635            || attempt.provider_approval.request.offer.provider != protocol_root.descriptor.provider
636        {
637            return Err(RegistrationLoadError::Invalid(
638                "device join cleanup executor is not the exact effective provider administrator"
639                    .to_string(),
640            ));
641        }
642        reference
643            .verify(&receipt, activating_author)
644            .and_then(|_| receipt.verify(&attempt, activating_author))
645            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
646        match &receipt.administrator_terminal {
647            super::device_join::ProviderAdminJoinTerminal::Completed(_) => {}
648            super::device_join::ProviderAdminJoinTerminal::Cancelled(closure) => {
649                let administrator =
650                    load_registration_ref(storage, root, &closure.administrator_registration)
651                        .await
652                        .map_err(RegistrationLoadError::Object)?
653                        .value;
654                closure
655                    .verify(&administrator)
656                    .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
657            }
658            super::device_join::ProviderAdminJoinTerminal::WriteRevoked(revocation) => {
659                let executor = load_registration_ref(storage, root, &revocation.executor)
660                    .await
661                    .map_err(RegistrationLoadError::Object)?
662                    .value;
663                revocation
664                    .verify(&executor)
665                    .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
666            }
667        }
668        match &receipt.joiner_terminal {
669            super::device_join::JoinerJoinTerminal::Ready(_) => {}
670            super::device_join::JoinerJoinTerminal::Cancelled(closure) => closure
671                .verify()
672                .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?,
673            super::device_join::JoinerJoinTerminal::WriteRevoked(revocation) => {
674                let executor = load_registration_ref(storage, root, &revocation.executor)
675                    .await
676                    .map_err(RegistrationLoadError::Object)?
677                    .value;
678                revocation
679                    .verify(&executor)
680                    .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
681            }
682        }
683    }
684    Ok(())
685}
686
687async fn validate_commit_join_outcomes(
688    storage: &dyn SyncStorage,
689    root: &StoreRootRef,
690    commit: &StoreBatchCommit,
691    activating_author: &StoreDeviceRegistration,
692    predecessor: Option<&RegistrationPredecessorAuthority<'_>>,
693) -> Result<(), RegistrationLoadError> {
694    if commit.device_join_outcomes().is_empty() {
695        return Ok(());
696    }
697    let predecessor = predecessor.ok_or_else(|| {
698        RegistrationLoadError::Invalid(
699            "device join outcome activation has no exact predecessor authority".to_string(),
700        )
701    })?;
702    if !predecessor.verifies_active_owner(&activating_author.author_pubkey) {
703        return Err(RegistrationLoadError::Invalid(
704            "device join outcome activation author is not an active Owner at its predecessor"
705                .to_string(),
706        ));
707    }
708    for outcome_ref in commit.device_join_outcomes() {
709        if !predecessor_contains_join_attempt(storage, root, &commit.order, outcome_ref.attempt())
710            .await?
711        {
712            return Err(RegistrationLoadError::Invalid(
713                "device join outcome names an attempt absent from its predecessor history"
714                    .to_string(),
715            ));
716        }
717        let attempt_context = ProtocolObjectContext::store(
718            root.store_root_hash,
719            ProtocolObjectDomain::DeviceJoinAttempt,
720        );
721        let attempt_bytes = storage
722            .read_protocol_object(
723                &attempt_context,
724                &outcome_ref.attempt().object,
725                &super::store_commit::device_join_attempt_semantic_prefix(
726                    outcome_ref.attempt().attempt_id,
727                ),
728            )
729            .await
730            .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
731        let unverified: DeviceJoinAttempt = serde_json::from_slice(&attempt_bytes)
732            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
733        let owner = load_registration_ref(storage, root, &unverified.owner_registration)
734            .await
735            .map_err(RegistrationLoadError::Object)?
736            .value;
737        let attempt = DeviceJoinAttempt::parse_at(&attempt_bytes, outcome_ref.attempt(), &owner)
738            .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
739        if owner != *activating_author
740            || attempt.owner_registration != commit.author_registration
741            || outcome_ref.slot() != &attempt.outcome_slot
742        {
743            return Err(RegistrationLoadError::Invalid(
744                "device join outcome differs from its exact Owner attempt".to_string(),
745            ));
746        }
747        let outcome = load_device_join_outcome_ref(storage, root, outcome_ref, &owner)
748            .await
749            .map_err(RegistrationLoadError::Object)?
750            .value;
751        if outcome.owner_registration != attempt.owner_registration
752            || outcome.owner_grant != attempt.owner_grant
753        {
754            return Err(RegistrationLoadError::Invalid(
755                "device join outcome signer differs from its attempt".to_string(),
756            ));
757        }
758        let activation = commit.device_registrations().iter().find(|activation| {
759            matches!(
760                &activation.authority,
761                StoreDeviceRegistrationActivationRef::Join { outcome, .. }
762                    if outcome == outcome_ref
763            )
764        });
765        if matches!(outcome.body, DeviceJoinOutcomeBody::Activated { .. }) != activation.is_some() {
766            return Err(RegistrationLoadError::Invalid(
767                "device join outcome and registration activation are not one closed operation"
768                    .to_string(),
769            ));
770        }
771    }
772    Ok(())
773}
774
775async fn validate_commit_join_attempts(
776    storage: &dyn SyncStorage,
777    root: &StoreRootRef,
778    commit: &StoreBatchCommit,
779    activating_author: &StoreDeviceRegistration,
780    predecessor: Option<&RegistrationPredecessorAuthority<'_>>,
781) -> Result<(), RegistrationLoadError> {
782    if commit.device_join_attempts().is_empty() {
783        return Ok(());
784    }
785    let predecessor = predecessor.ok_or_else(|| {
786        RegistrationLoadError::Invalid(
787            "device join attempt activation has no exact predecessor membership authority"
788                .to_string(),
789        )
790    })?;
791    if !predecessor.verifies_active_owner(&activating_author.author_pubkey) {
792        return Err(RegistrationLoadError::Invalid(
793            "device join attempt activation author is not an active Owner at its predecessor"
794                .to_string(),
795        ));
796    }
797    let bootstrap_cut = commit
798        .order
799        .predecessor_cut()
800        .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
801    for reference in commit.device_join_attempts() {
802        let attempt = Box::pin(load_device_join_attempt_ref(
803            storage,
804            root,
805            reference,
806            activating_author,
807        ))
808        .await
809        .map_err(RegistrationLoadError::Object)?
810        .value;
811        if attempt.owner_registration != commit.author_registration
812            || attempt.membership != commit.membership_state
813            || attempt.bootstrap_cut != bootstrap_cut
814            || !predecessor.verifies_owner(
815                &attempt.membership,
816                &activating_author.author_pubkey,
817                &attempt.owner_grant,
818            )
819        {
820            return Err(RegistrationLoadError::Invalid(
821                "device join attempt differs from its exact activating predecessor authority"
822                    .to_string(),
823            ));
824        }
825    }
826    Ok(())
827}
828
829async fn registration_activation(
830    storage: &dyn SyncStorage,
831    root: &StoreRootRef,
832    activated: &ActivatedStoreDeviceRegistrationRef,
833    registration: &StoreDeviceRegistration,
834    activating_author: &StoreDeviceRegistration,
835    activating_order: &super::store_commit::StoreCommitOrder,
836    serial_recovery_activation: Option<&super::store_commit::SerialRecoveryActivation>,
837    predecessor: &RegistrationPredecessorAuthority<'_>,
838) -> Result<StoreDeviceRegistrationActivation, RegistrationLoadError> {
839    if !predecessor.verifies_active_owner(&activating_author.author_pubkey) {
840        return Err(RegistrationLoadError::Invalid(
841            "registration activation commit author is not an active Owner at its predecessor"
842                .to_string(),
843        ));
844    }
845    match (&registration.origin, &activated.authority) {
846        (
847            StoreDeviceRegistrationOrigin::Join {
848                attempt_id: origin_attempt,
849                outcome_slot,
850                ..
851            },
852            StoreDeviceRegistrationActivationRef::Join {
853                attempt_id,
854                outcome,
855            },
856        ) if origin_attempt == attempt_id && outcome_slot == outcome.slot() => {
857            let attempt_context = ProtocolObjectContext::store(
858                root.store_root_hash,
859                ProtocolObjectDomain::DeviceJoinAttempt,
860            );
861            let attempt_prefix =
862                super::store_commit::device_join_attempt_semantic_prefix(*attempt_id);
863            let attempt_bytes = storage
864                .read_protocol_object(&attempt_context, &outcome.attempt().object, &attempt_prefix)
865                .await
866                .map_err(|error| RegistrationLoadError::Object(StoreObjectError::Storage(error)))?;
867            let unverified_attempt: DeviceJoinAttempt = serde_json::from_slice(&attempt_bytes)
868                .map_err(|error| {
869                    RegistrationLoadError::Invalid(format!("device join attempt: {error}"))
870                })?;
871            let owner =
872                load_registration_ref(storage, root, &unverified_attempt.owner_registration)
873                    .await
874                    .map_err(RegistrationLoadError::Object)?
875                    .value;
876            let attempt = load_device_join_attempt_ref(storage, root, outcome.attempt(), &owner)
877                .await
878                .map_err(RegistrationLoadError::Object)?
879                .value;
880            if !predecessor_contains_join_attempt(
881                storage,
882                root,
883                activating_order,
884                outcome.attempt(),
885            )
886            .await?
887                || attempt.expected_registration != *registration
888                || attempt.registration_slot != *activated.registration.object.slot()
889                || !predecessor.verifies_owner(
890                    &attempt.membership,
891                    &owner.author_pubkey,
892                    &attempt.owner_grant,
893                )
894            {
895                return Err(RegistrationLoadError::Invalid(
896                    "activated registration differs from its exact join attempt".to_string(),
897                ));
898            }
899            let outcome_value = load_device_join_outcome_ref(storage, root, outcome, &owner)
900                .await
901                .map_err(RegistrationLoadError::Object)?
902                .value;
903            if outcome_value.owner_registration != attempt.owner_registration
904                || outcome_value.owner_grant != attempt.owner_grant
905            {
906                return Err(RegistrationLoadError::Invalid(
907                    "join outcome signer differs from its exact attempt authority".to_string(),
908                ));
909            }
910            let DeviceJoinOutcomeBody::Activated { readiness } = &outcome_value.body else {
911                return Err(RegistrationLoadError::Invalid(
912                    "cancelled device join outcome cannot activate a registration".to_string(),
913                ));
914            };
915            let initial_ack =
916                load_store_ack_ref(storage, root, &readiness.initial_ack, registration)
917                    .await
918                    .map_err(RegistrationLoadError::Object)?
919                    .value;
920            readiness
921                .verify(
922                    outcome.attempt(),
923                    &attempt,
924                    registration,
925                    &readiness.initial_ack,
926                    &initial_ack,
927                )
928                .map_err(|error| RegistrationLoadError::Invalid(error.to_string()))?;
929            Ok(StoreDeviceRegistrationActivation::Join {
930                attempt_id: *attempt_id,
931                outcome: outcome.clone(),
932            })
933        }
934        (
935            StoreDeviceRegistrationOrigin::Recovery {
936                recovery_id: origin_recovery,
937                recovery_slot,
938                ..
939            },
940            StoreDeviceRegistrationActivationRef::Recovery { recovery_id, node },
941        ) if origin_recovery == recovery_id && recovery_slot == node.slot() => {
942            if matches!(predecessor, RegistrationPredecessorAuthority::Serial { .. })
943                && serial_recovery_activation.is_none_or(|body| &body.registration != activated)
944            {
945                return Err(RegistrationLoadError::Invalid(
946                    "Serial recovery activation differs from its closed commit body".to_string(),
947                ));
948            }
949            let node_value = load_owner_recovery_node_ref(storage, root, node)
950                .await
951                .map_err(RegistrationLoadError::Object)?
952                .value;
953            let mut reached_ref = node.clone();
954            let mut reached = node_value.clone();
955            while let Some(predecessor_ref) = reached.predecessor.clone() {
956                let predecessor = load_owner_recovery_node_ref(storage, root, &predecessor_ref)
957                    .await
958                    .map_err(RegistrationLoadError::Object)?
959                    .value;
960                if predecessor.next_slot != *reached_ref.object.slot() {
961                    return Err(RegistrationLoadError::Invalid(
962                        "recovery node does not occupy its exact predecessor successor slot"
963                            .to_string(),
964                    ));
965                }
966                if predecessor.recovery_id != node_value.recovery_id {
967                    return Err(RegistrationLoadError::Invalid(
968                        "recovery predecessor belongs to another recovery operation".to_string(),
969                    ));
970                }
971                reached_ref = predecessor_ref;
972                reached = predecessor;
973            }
974            if node_value.recovery_id != *recovery_id
975                || node_value.readiness.registration != activated.registration
976                || node_value.next_slot == *node.object.slot()
977                || registration.author_pubkey != node_value.owner_pubkey
978                || !predecessor.verifies_owner(
979                    &node_value.membership,
980                    &node_value.owner_pubkey,
981                    &node_value.owner_grant,
982                )
983            {
984                return Err(RegistrationLoadError::Invalid(
985                    "recovery node differs from its exact registration".to_string(),
986                ));
987            }
988            let initial_ack = load_store_ack_ref(
989                storage,
990                root,
991                &node_value.readiness.initial_ack,
992                registration,
993            )
994            .await
995            .map_err(RegistrationLoadError::Object)?
996            .value;
997            if initial_ack.revision != 1
998                || initial_ack.predecessor.is_some()
999                || initial_ack.author_registration != activated.registration
1000                || initial_ack.store_cut != node_value.readiness.bootstrap_cut
1001            {
1002                return Err(RegistrationLoadError::Invalid(
1003                    "recovery readiness differs from its initial acknowledgement".to_string(),
1004                ));
1005            }
1006            Ok(StoreDeviceRegistrationActivation::Recovery {
1007                recovery_id: *recovery_id,
1008                node: node.clone(),
1009            })
1010        }
1011        _ => Err(RegistrationLoadError::Invalid(format!(
1012            "Store registration {} origin differs from its activation authority",
1013            registration.device_id
1014        ))),
1015    }
1016}
1017
1018async fn predecessor_contains_join_attempt(
1019    storage: &dyn SyncStorage,
1020    root: &StoreRootRef,
1021    order: &super::store_commit::StoreCommitOrder,
1022    expected: &super::store_commit::DeviceJoinAttemptRef,
1023) -> Result<bool, RegistrationLoadError> {
1024    let mut pending = match order {
1025        super::store_commit::StoreCommitOrder::MergeConcurrent {
1026            predecessor,
1027            dependencies,
1028            ..
1029        } => predecessor
1030            .iter()
1031            .chain(dependencies.values())
1032            .cloned()
1033            .collect::<Vec<_>>(),
1034        super::store_commit::StoreCommitOrder::Serial {
1035            predecessor: super::store_commit::StoreSerialPredecessor::Commit(predecessor),
1036            ..
1037        } => vec![predecessor.clone()],
1038        super::store_commit::StoreCommitOrder::Serial {
1039            predecessor: super::store_commit::StoreSerialPredecessor::Genesis { .. },
1040            ..
1041        } => Vec::new(),
1042    };
1043    let mut visited = BTreeSet::new();
1044    while let Some(reference) = pending.pop() {
1045        if !visited.insert(reference.clone()) {
1046            continue;
1047        }
1048        let (commit, _) = load_commit_with_author(storage, root, &reference)
1049            .await
1050            .map_err(RegistrationLoadError::Object)?;
1051        if commit
1052            .device_join_attempts()
1053            .binary_search(expected)
1054            .is_ok()
1055        {
1056            return Ok(true);
1057        }
1058        match commit.order {
1059            super::store_commit::StoreCommitOrder::MergeConcurrent {
1060                predecessor,
1061                dependencies,
1062                ..
1063            } => {
1064                pending.extend(predecessor);
1065                pending.extend(dependencies.into_values());
1066            }
1067            super::store_commit::StoreCommitOrder::Serial {
1068                predecessor: super::store_commit::StoreSerialPredecessor::Commit(predecessor),
1069                ..
1070            } => pending.push(predecessor),
1071            super::store_commit::StoreCommitOrder::Serial {
1072                predecessor: super::store_commit::StoreSerialPredecessor::Genesis { .. },
1073                ..
1074            } => {}
1075        }
1076    }
1077    Ok(false)
1078}
1079
1080async fn predecessor_contains_join_outcome(
1081    storage: &dyn SyncStorage,
1082    root: &StoreRootRef,
1083    order: &super::store_commit::StoreCommitOrder,
1084    expected: &super::store_commit::DeviceJoinOutcomeRef,
1085) -> Result<bool, RegistrationLoadError> {
1086    let mut pending = match order {
1087        super::store_commit::StoreCommitOrder::MergeConcurrent {
1088            predecessor,
1089            dependencies,
1090            ..
1091        } => predecessor
1092            .iter()
1093            .chain(dependencies.values())
1094            .cloned()
1095            .collect::<Vec<_>>(),
1096        super::store_commit::StoreCommitOrder::Serial {
1097            predecessor: super::store_commit::StoreSerialPredecessor::Commit(predecessor),
1098            ..
1099        } => vec![predecessor.clone()],
1100        super::store_commit::StoreCommitOrder::Serial {
1101            predecessor: super::store_commit::StoreSerialPredecessor::Genesis { .. },
1102            ..
1103        } => Vec::new(),
1104    };
1105    let mut visited = BTreeSet::new();
1106    while let Some(reference) = pending.pop() {
1107        if !visited.insert(reference.clone()) {
1108            continue;
1109        }
1110        let (commit, _) = load_commit_with_author(storage, root, &reference)
1111            .await
1112            .map_err(RegistrationLoadError::Object)?;
1113        if commit
1114            .device_join_outcomes()
1115            .binary_search(expected)
1116            .is_ok()
1117        {
1118            return Ok(true);
1119        }
1120        match commit.order {
1121            super::store_commit::StoreCommitOrder::MergeConcurrent {
1122                predecessor,
1123                dependencies,
1124                ..
1125            } => {
1126                pending.extend(predecessor);
1127                pending.extend(dependencies.into_values());
1128            }
1129            super::store_commit::StoreCommitOrder::Serial {
1130                predecessor: super::store_commit::StoreSerialPredecessor::Commit(predecessor),
1131                ..
1132            } => pending.push(predecessor),
1133            super::store_commit::StoreCommitOrder::Serial {
1134                predecessor: super::store_commit::StoreSerialPredecessor::Genesis { .. },
1135                ..
1136            } => {}
1137        }
1138    }
1139    Ok(false)
1140}
1141
1142#[doc(hidden)]
1143pub struct SerialResolutionCommit {
1144    pub(crate) commit: StoreBatchCommit,
1145    pub(crate) commit_ref: super::store_commit::StoreBatchCommitRef,
1146    pub(crate) package: Option<Vec<u8>>,
1147    pub(crate) cleanup: Vec<LocalBlobCleanupIntent>,
1148    pub(crate) registrations: Vec<(
1149        StoreDeviceRegistration,
1150        super::store_commit::StoreDeviceRegistrationActivation,
1151    )>,
1152    pub(crate) circle_activations: Vec<super::circle_ops::VerifiedCircleReference>,
1153    pub(crate) authorization_after: SerialAuthorizationState,
1154}
1155
1156#[doc(hidden)]
1157pub struct SerialResolutionPlan {
1158    pub(crate) head: StoreSerialHead,
1159    pub(crate) head_object: super::storage::VersionedObject,
1160    pub(crate) commits: Vec<SerialResolutionCommit>,
1161}
1162
1163enum ApplyOutcome {
1164    Applied(Vec<RowChange>),
1165    Held(HeldStorePositionReason),
1166}
1167
1168/// Discover every visible immutable head, then repeatedly materialize any commit
1169/// whose exact predecessor and dependency positions are already durable.
1170#[allow(clippy::too_many_arguments)]
1171pub async fn pull_store_commits(
1172    db: &Database,
1173    tables: &[SyncedTable],
1174    storage: &dyn SyncStorage,
1175    store_root_hash: ObjectHash,
1176    store_dir: &StoreDir,
1177    membership: Option<&MembershipChain>,
1178) -> Result<StorePullResult, StorePullError> {
1179    pull_store_commits_with_identity(
1180        db,
1181        tables,
1182        storage,
1183        None,
1184        store_root_hash,
1185        store_dir,
1186        membership,
1187        None,
1188    )
1189    .await
1190}
1191
1192#[allow(clippy::too_many_arguments)]
1193#[doc(hidden)]
1194pub async fn pull_store_commits_with_coordination(
1195    db: &Database,
1196    tables: &[SyncedTable],
1197    storage: &dyn SyncStorage,
1198    serial_coordination: Option<&dyn CoordinationStorage>,
1199    store_root_hash: ObjectHash,
1200    store_dir: &StoreDir,
1201    membership: Option<&MembershipChain>,
1202) -> Result<StorePullResult, StorePullError> {
1203    pull_store_commits_with_identity(
1204        db,
1205        tables,
1206        storage,
1207        serial_coordination,
1208        store_root_hash,
1209        store_dir,
1210        membership,
1211        None,
1212    )
1213    .await
1214}
1215
1216#[allow(clippy::too_many_arguments)]
1217pub async fn pull_store_commits_with_identity(
1218    db: &Database,
1219    tables: &[SyncedTable],
1220    storage: &dyn SyncStorage,
1221    serial_coordination: Option<&dyn CoordinationStorage>,
1222    store_root_hash: ObjectHash,
1223    store_dir: &StoreDir,
1224    membership: Option<&MembershipChain>,
1225    identity: Option<&crate::keys::UserKeypair>,
1226) -> Result<StorePullResult, StorePullError> {
1227    let root = db
1228        .local_store_root_ref()
1229        .await
1230        .map_err(|error| StorePullError::Database(format!("load exact Store root: {error}")))?
1231        .ok_or_else(|| {
1232            StorePullError::Database("Store root exact reference is absent".to_string())
1233        })?;
1234    if root.store_root_hash != store_root_hash {
1235        return Err(StorePullError::Database(
1236            "requested Store root differs from the durable exact root reference".to_string(),
1237        ));
1238    }
1239    let verified_root = load_store_protocol_root(storage, &root).await?.value;
1240    if db.write_policy() == crate::WritePolicy::Serial {
1241        return pull_serial_store_commits(
1242            db,
1243            tables,
1244            storage,
1245            serial_coordination.ok_or_else(|| {
1246                StorePullError::Serial("coordination capability is absent".to_string())
1247            })?,
1248            &root,
1249            verified_root,
1250            store_dir,
1251            identity,
1252        )
1253        .await;
1254    }
1255    if verified_root.descriptor.write_policy != crate::WritePolicy::MergeConcurrent {
1256        return Err(StorePullError::Database(
1257            "durable write policy differs from the signed Store root".to_string(),
1258        ));
1259    }
1260
1261    let mut active = load_active_merge_registrations(db, storage, &root)
1262        .await
1263        .map_err(|error| {
1264            StorePullError::Database(format!("load active Merge registrations: {error}"))
1265        })?;
1266    if let Some(membership) = membership {
1267        for recovered in
1268            discover_merge_owner_recoveries(storage, &root, &verified_root, membership).await?
1269        {
1270            if active
1271                .iter()
1272                .all(|(reference, _)| reference != &recovered.0)
1273            {
1274                active.push(recovered);
1275            }
1276        }
1277    }
1278    let mut candidates = BTreeMap::new();
1279    let mut visible_heads = Vec::new();
1280    let mut held = Vec::new();
1281    for (registration_ref, registration) in active {
1282        let discovered = discover_merge_stream(storage, &root, &registration_ref, &registration)
1283            .await
1284            .map_err(|error| {
1285                StorePullError::Database(format!(
1286                    "discover Merge stream for {}: {error}",
1287                    registration.device_id
1288                ))
1289            })?;
1290        if let Some(head) = discovered.latest_head {
1291            visible_heads.push(VerifiedStoreDeviceHead {
1292                head,
1293                author: registration.clone(),
1294            });
1295        }
1296        held.extend(discovered.held);
1297        for (commit_ref, commit) in discovered.commits {
1298            if commit_ref.coord.sequence() != commit.seq() {
1299                held.push(held_commit(
1300                    &commit_ref,
1301                    HeldStorePositionReason::InvalidObject(
1302                        "exact commit coordinate differs from signed sequence".to_string(),
1303                    ),
1304                ));
1305                continue;
1306            }
1307            let stream_id = commit_stream_id(&commit_ref.coord);
1308            if let Some(materialized) = db
1309                .exact_materialized_ref(&stream_id, commit_ref.coord.sequence())
1310                .await?
1311            {
1312                if materialized == commit_ref {
1313                    continue;
1314                }
1315                held.push(held_commit(
1316                    &commit_ref,
1317                    HeldStorePositionReason::HashMismatch {
1318                        referenced_device_id: stream_id,
1319                        referenced_commit: commit_ref.clone(),
1320                        materialized_hash: materialized.commit_hash,
1321                    },
1322                ));
1323                continue;
1324            }
1325            if commit
1326                .store_package()
1327                .as_ref()
1328                .is_some_and(|package| package.schema_version > db.schema_version())
1329            {
1330                let required = commit
1331                    .store_package()
1332                    .as_ref()
1333                    .expect("checked Store package")
1334                    .schema_version;
1335                held.push(held_commit(
1336                    &commit_ref,
1337                    HeldStorePositionReason::NewerSchema {
1338                        local: db.schema_version(),
1339                        required,
1340                    },
1341                ));
1342                continue;
1343            }
1344            let predecessor_membership = if commit.device_join_attempts().is_empty()
1345                && commit.device_join_outcomes().is_empty()
1346                && commit.device_join_abandonments().is_empty()
1347                && commit.device_join_cleanup_receipts().is_empty()
1348                && commit.device_registrations().is_empty()
1349            {
1350                None
1351            } else {
1352                match load_merge_predecessor_membership(storage, &root, &commit.membership_state)
1353                    .await
1354                {
1355                    Ok(membership) => Some(membership),
1356                    Err(RegistrationLoadError::Object(error)) => {
1357                        held.push(held_commit(&commit_ref, held_object_error(error)));
1358                        continue;
1359                    }
1360                    Err(RegistrationLoadError::Invalid(error)) => {
1361                        held.push(held_commit(
1362                            &commit_ref,
1363                            HeldStorePositionReason::InvalidObject(error),
1364                        ));
1365                        continue;
1366                    }
1367                }
1368            };
1369            let predecessor_authority = predecessor_membership
1370                .as_ref()
1371                .map(RegistrationPredecessorAuthority::MergeConcurrent);
1372            let registrations = match load_commit_registrations(
1373                storage,
1374                &root,
1375                &commit,
1376                &registration,
1377                predecessor_authority.as_ref(),
1378            )
1379            .await
1380            {
1381                Ok(registrations) => registrations,
1382                Err(RegistrationLoadError::Object(error)) => {
1383                    held.push(held_commit(&commit_ref, held_object_error(error)));
1384                    continue;
1385                }
1386                Err(RegistrationLoadError::Invalid(error)) => {
1387                    held.push(held_commit(
1388                        &commit_ref,
1389                        HeldStorePositionReason::InvalidObject(error),
1390                    ));
1391                    continue;
1392                }
1393            };
1394            if !membership_authorizes(membership, &commit, &registration) {
1395                held.push(held_commit(
1396                    &commit_ref,
1397                    HeldStorePositionReason::Unauthorized,
1398                ));
1399                continue;
1400            }
1401            let package = match load_store_package(storage, &commit_ref, &commit).await {
1402                Ok(package) => package.map(|package| package.value),
1403                Err(error) => {
1404                    held.push(held_package(&commit_ref, &commit, held_object_error(error)));
1405                    continue;
1406                }
1407            };
1408            let circle_activations = match load_pull_circle_activations(
1409                db,
1410                storage,
1411                &root,
1412                &commit_ref,
1413                &commit,
1414                &registration,
1415                identity,
1416            )
1417            .await
1418            {
1419                Ok(activations) => activations,
1420                Err(PullCircleActivationError::Database(error)) => return Err(error.into()),
1421                Err(PullCircleActivationError::Invalid(error)) => {
1422                    held.push(held_commit(
1423                        &commit_ref,
1424                        HeldStorePositionReason::InvalidObject(error),
1425                    ));
1426                    continue;
1427                }
1428            };
1429            let key = (
1430                commit_stream_id(&commit_ref.coord),
1431                commit_ref.coord.sequence(),
1432            );
1433            candidates.insert(
1434                key,
1435                Candidate {
1436                    commit_ref,
1437                    commit,
1438                    author: registration.clone(),
1439                    package,
1440                    registrations,
1441                    circle_activations,
1442                },
1443            );
1444        }
1445    }
1446
1447    let schema: Arc<TableSchema> = {
1448        let tables = tables.to_vec();
1449        Arc::new(
1450            db.call(move |conn| TableSchema::from_db(conn, &tables))
1451                .await
1452                .map_err(|error| {
1453                    StorePullError::Database(format!("load synced table schema: {error}"))
1454                })?,
1455        )
1456    };
1457    let coverage = db.snapshot_coverage_frontier().await.map_err(|error| {
1458        StorePullError::Database(format!("load snapshot coverage frontier: {error}"))
1459    })?;
1460    let mut frontier = db.materialized_frontier().await.map_err(|error| {
1461        StorePullError::Database(format!("load materialized frontier: {error}"))
1462    })?;
1463    let mut applied_devices = BTreeSet::new();
1464    let mut row_changes = Vec::new();
1465    let mut changesets_applied = 0_u64;
1466    let mut asset_downloads_failed = false;
1467    let mut blocked = BTreeMap::new();
1468
1469    loop {
1470        let mut progressed = false;
1471        let keys = candidates.keys().cloned().collect::<Vec<_>>();
1472        for key in keys {
1473            let candidate = candidates
1474                .get(&key)
1475                .expect("candidate key came from the same map");
1476            match readiness(
1477                db,
1478                storage,
1479                &root,
1480                &coverage,
1481                &frontier,
1482                &candidate.commit_ref,
1483                &candidate.commit,
1484            )
1485            .await
1486            .map_err(|error| {
1487                StorePullError::Database(format!(
1488                    "evaluate Store commit readiness for {}/{}: {error}",
1489                    key.0, key.1
1490                ))
1491            })? {
1492                Readiness::AlreadyMaterialized => {
1493                    candidates.remove(&key);
1494                    blocked.remove(&key);
1495                    progressed = true;
1496                }
1497                Readiness::Held(held_position) => {
1498                    blocked.insert(key, held_position);
1499                }
1500                Readiness::Ready => {
1501                    let candidate = candidates
1502                        .remove(&key)
1503                        .expect("ready candidate remains present");
1504                    match apply_candidate(db, storage, store_dir, schema.clone(), &candidate)
1505                        .await
1506                        .map_err(|error| {
1507                            StorePullError::Database(format!(
1508                                "materialize Store commit {}/{}: {error}",
1509                                key.0, key.1
1510                            ))
1511                        })? {
1512                        ApplyOutcome::Applied(changes) => {
1513                            let stream_id = commit_stream_id(&candidate.commit_ref.coord);
1514                            frontier.insert(stream_id.clone(), candidate.commit_ref.clone());
1515                            applied_devices.insert(stream_id);
1516                            row_changes.extend(changes);
1517                            changesets_applied =
1518                                changesets_applied.checked_add(1).ok_or_else(|| {
1519                                    StorePullError::Database(
1520                                        "Store apply count exceeded u64".to_string(),
1521                                    )
1522                                })?;
1523                            blocked.remove(&key);
1524                            progressed = true;
1525                        }
1526                        ApplyOutcome::Held(reason) => {
1527                            if matches!(reason, HeldStorePositionReason::BlobDownloadFailed) {
1528                                asset_downloads_failed = true;
1529                            }
1530                            let held_position = held_commit(&candidate.commit_ref, reason);
1531                            candidates.insert(key.clone(), candidate);
1532                            blocked.insert(key, held_position);
1533                        }
1534                    }
1535                }
1536            }
1537        }
1538        if !progressed {
1539            break;
1540        }
1541    }
1542
1543    held.extend(blocked.into_values());
1544    held.sort_by(|left, right| {
1545        (left.coordinate.device_id(), left.coordinate.seq())
1546            .cmp(&(right.coordinate.device_id(), right.coordinate.seq()))
1547    });
1548    let local_blob_cleanup_pending =
1549        local_cleanup::drain(db, store_dir).await.map_err(|error| {
1550            StorePullError::Database(format!("drain local blob cleanup intents: {error}"))
1551        })?;
1552
1553    Ok(StorePullResult {
1554        changesets_applied,
1555        devices_pulled: u64::try_from(applied_devices.len()).expect("device count fits in u64"),
1556        held_positions: held,
1557        visible_heads,
1558        serial_head: None,
1559        row_changes,
1560        asset_downloads_failed,
1561        local_blob_cleanup_pending,
1562        frontier,
1563    })
1564}
1565
1566struct MergeStreamDiscovery {
1567    latest_head: Option<StoreDeviceHead>,
1568    commits: Vec<(StoreBatchCommitRef, StoreBatchCommit)>,
1569    held: Vec<HeldStorePosition>,
1570}
1571
1572async fn load_active_merge_registrations(
1573    db: &Database,
1574    storage: &dyn SyncStorage,
1575    root: &StoreRootRef,
1576) -> Result<Vec<(StoreDeviceRegistrationRef, StoreDeviceRegistration)>, StorePullError> {
1577    let durable = db.activated_store_device_registration_records().await?;
1578    let mut verified = Vec::with_capacity(durable.len());
1579    for (reference, expected) in durable {
1580        let opened = load_registration_ref(storage, root, &reference).await?;
1581        if opened.value != expected {
1582            return Err(StorePullError::Database(format!(
1583                "activated Store registration {} differs from its exact remote bytes",
1584                reference.device_id
1585            )));
1586        }
1587        if !matches!(
1588            opened.value.store_commits,
1589            StoreCommitAnchor::MergeConcurrent {
1590                announcements: DeviceStreamAnchor::StoreAnnouncements { .. }
1591            }
1592        ) {
1593            return Err(StorePullError::Database(format!(
1594                "activated Store registration {} has no Merge announcement anchor",
1595                reference.device_id
1596            )));
1597        }
1598        verified.push((reference, opened.value));
1599    }
1600    Ok(verified)
1601}
1602
1603async fn discover_merge_owner_recoveries(
1604    storage: &dyn SyncStorage,
1605    root: &StoreRootRef,
1606    protocol: &super::store_commit::StoreProtocolRoot,
1607    membership: &MembershipChain,
1608) -> Result<Vec<(StoreDeviceRegistrationRef, StoreDeviceRegistration)>, StorePullError> {
1609    if membership
1610        .active_owner_grant(&protocol.descriptor.founder_pubkey)
1611        .as_ref()
1612        != Some(&protocol.descriptor.founder_grant)
1613    {
1614        return Ok(Vec::new());
1615    }
1616    let super::store_commit::GrantStreamAnchor::OwnerRecovery { first_slot } =
1617        &protocol.descriptor.founder_recovery
1618    else {
1619        return Err(StorePullError::Database(
1620            "Store founder recovery authority has no recovery stream".into(),
1621        ));
1622    };
1623    let context = ProtocolObjectContext::store(
1624        root.store_root_hash,
1625        ProtocolObjectDomain::OwnerRecoveryNode,
1626    );
1627    let authority = RegistrationPredecessorAuthority::MergeConcurrent(membership);
1628    let mut slot = first_slot.clone();
1629    let mut predecessor: Option<OwnerRecoveryNodeRef> = None;
1630    let mut sequence = 1_u64;
1631    let mut recovered = Vec::new();
1632    loop {
1633        let prefix = super::store_commit::owner_recovery_semantic_prefix(
1634            &protocol.descriptor.founder_pubkey,
1635            protocol.descriptor.founder_grant.clone(),
1636            sequence,
1637        );
1638        let (bytes, object) = match storage.read_protocol_slot(&context, &slot, &prefix).await {
1639            Ok(opened) => opened,
1640            Err(StorageError::NotFound(_)) => break,
1641            Err(error) => return Err(StoreObjectError::Storage(error).into()),
1642        };
1643        let unverified: OwnerRecoveryNode = serde_json::from_slice(&bytes)
1644            .map_err(|error| StorePullError::Database(format!("Owner recovery node: {error}")))?;
1645        let reference = OwnerRecoveryNodeRef {
1646            owner_pubkey: unverified.owner_pubkey.clone(),
1647            owner_grant: unverified.owner_grant.clone(),
1648            sequence: unverified.sequence,
1649            node_hash: unverified.node_hash(),
1650            object,
1651        };
1652        let node = OwnerRecoveryNode::parse_at(&bytes, root, &reference)
1653            .map_err(|error| StorePullError::Database(error.to_string()))?;
1654        if reference.owner_pubkey != protocol.descriptor.founder_pubkey
1655            || reference.owner_grant != protocol.descriptor.founder_grant
1656            || reference.sequence != sequence
1657            || node.predecessor != predecessor
1658            || !authority.verifies_owner(&node.membership, &node.owner_pubkey, &node.owner_grant)
1659        {
1660            return Err(StorePullError::Database(
1661                "Owner recovery stream differs from its root-anchored authority".into(),
1662            ));
1663        }
1664        let registration = load_registration_ref(storage, root, &node.readiness.registration)
1665            .await?
1666            .value;
1667        let initial_ack =
1668            load_store_ack_ref(storage, root, &node.readiness.initial_ack, &registration)
1669                .await?
1670                .value;
1671        let origin_matches = matches!(
1672            &registration.origin,
1673            StoreDeviceRegistrationOrigin::Recovery {
1674                recovery_id,
1675                recovery_slot,
1676                owner_grant,
1677            } if *recovery_id == node.recovery_id
1678                && recovery_slot == reference.slot()
1679                && owner_grant == &node.owner_grant
1680        );
1681        if !origin_matches
1682            || registration.author_pubkey != node.owner_pubkey
1683            || initial_ack.revision != 1
1684            || initial_ack.predecessor.is_some()
1685            || initial_ack.store_cut != node.readiness.bootstrap_cut
1686            || initial_ack.author_registration != node.readiness.registration
1687        {
1688            return Err(StorePullError::Database(
1689                "Owner recovery readiness differs from its registration graph".into(),
1690            ));
1691        }
1692        recovered.push((node.readiness.registration.clone(), registration));
1693        slot = node.next_slot.clone();
1694        predecessor = Some(reference);
1695        sequence = sequence
1696            .checked_add(1)
1697            .ok_or_else(|| StorePullError::Database("Owner recovery sequence overflow".into()))?;
1698    }
1699    Ok(recovered)
1700}
1701
1702async fn discover_merge_stream(
1703    storage: &dyn SyncStorage,
1704    root: &StoreRootRef,
1705    registration_ref: &StoreDeviceRegistrationRef,
1706    registration: &StoreDeviceRegistration,
1707) -> Result<MergeStreamDiscovery, StorePullError> {
1708    let StoreCommitAnchor::MergeConcurrent {
1709        announcements: DeviceStreamAnchor::StoreAnnouncements { first_slot },
1710    } = &registration.store_commits
1711    else {
1712        return Err(StorePullError::Database(format!(
1713            "Store registration {} has no Merge announcement anchor",
1714            registration.device_id
1715        )));
1716    };
1717    let stream_id = super::membership::AuthorStreamId::store_announcements(root, registration_ref);
1718    let activation = StreamActivationId::store_announcements(root, registration_ref);
1719    let context =
1720        ProtocolObjectContext::store(root.store_root_hash, ProtocolObjectDomain::StoreHead);
1721    let mut slot = first_slot.clone();
1722    let mut predecessor = None;
1723    let mut sequence = 1_u64;
1724    let mut latest_head = None;
1725    let mut commits = Vec::new();
1726    let mut held = Vec::new();
1727    let mut visited = BTreeSet::new();
1728
1729    loop {
1730        if !visited.insert(slot.clone()) {
1731            return Err(StorePullError::Database(format!(
1732                "Store announcement stream {stream_id} repeats a reserved slot"
1733            )));
1734        }
1735        let semantic_prefix = head_slot_prefix(&registration.device_id.to_string(), sequence);
1736        let (bytes, object) = match storage
1737            .read_protocol_slot(&context, &slot, &semantic_prefix)
1738            .await
1739        {
1740            Ok(opened) => opened,
1741            Err(StorageError::NotFound(_)) => break,
1742            Err(error) => return Err(StoreObjectError::Storage(error).into()),
1743        };
1744        let unverified: StoreDeviceHead = match serde_json::from_slice(&bytes) {
1745            Ok(head) => head,
1746            Err(error) => {
1747                held.push(HeldStorePosition {
1748                    coordinate: HeldStoreCoordinate::Head {
1749                        device_id: stream_id.to_string(),
1750                        seq: sequence,
1751                        head_hash: ObjectHash::digest(&bytes),
1752                    },
1753                    reason: HeldStorePositionReason::InvalidObject(error.to_string()),
1754                });
1755                break;
1756            }
1757        };
1758        let coord_matches = matches!(
1759            unverified.commit.coord,
1760            StoreCommitCoord::MergeConcurrent {
1761                stream_id: declared,
1762                sequence: declared_sequence,
1763            } if declared == stream_id && declared_sequence == sequence
1764        );
1765        if !coord_matches
1766            || unverified.author_registration != *registration_ref
1767            || unverified.successor.activation != activation
1768            || unverified.successor.predecessor != predecessor
1769        {
1770            held.push(HeldStorePosition {
1771                coordinate: HeldStoreCoordinate::Head {
1772                    device_id: stream_id.to_string(),
1773                    seq: sequence,
1774                    head_hash: unverified.head_hash(),
1775                },
1776                reason: HeldStorePositionReason::WrongSlot(
1777                    "Store head differs from its activated successor chain".to_string(),
1778                ),
1779            });
1780            break;
1781        }
1782        let commit = match load_commit_ref(
1783            storage,
1784            root.store_root_hash,
1785            &unverified.commit,
1786            registration,
1787        )
1788        .await
1789        {
1790            Ok(commit) => commit,
1791            Err(error) => {
1792                held.push(held_commit(&unverified.commit, held_object_error(error)));
1793                break;
1794            }
1795        };
1796        let head = match StoreDeviceHead::parse_at(
1797            &bytes,
1798            root.store_root_hash,
1799            registration,
1800            &unverified.commit,
1801        ) {
1802            Ok(head) => head,
1803            Err(error) => {
1804                held.push(HeldStorePosition {
1805                    coordinate: HeldStoreCoordinate::Head {
1806                        device_id: stream_id.to_string(),
1807                        seq: sequence,
1808                        head_hash: unverified.head_hash(),
1809                    },
1810                    reason: held_protocol_error(error),
1811                });
1812                break;
1813            }
1814        };
1815        let next_slot = head.successor.next_slot.clone();
1816        predecessor = Some(object);
1817        sequence = sequence.checked_add(1).ok_or_else(|| {
1818            StorePullError::Database(format!(
1819                "Store announcement stream {stream_id} sequence overflow"
1820            ))
1821        })?;
1822        commits.push((head.commit.clone(), commit.value));
1823        latest_head = Some(head);
1824        slot = next_slot;
1825    }
1826
1827    Ok(MergeStreamDiscovery {
1828        latest_head,
1829        commits,
1830        held,
1831    })
1832}
1833
1834fn held_protocol_error(error: StoreProtocolError) -> HeldStorePositionReason {
1835    match error {
1836        StoreProtocolError::InvalidSignature => HeldStorePositionReason::InvalidSignature,
1837        StoreProtocolError::RelocatedSlot { .. }
1838        | StoreProtocolError::RelocatedPackage { .. }
1839        | StoreProtocolError::StoreRootMismatch { .. }
1840        | StoreProtocolError::StoreMismatch { .. }
1841        | StoreProtocolError::FounderMismatch { .. } => {
1842            HeldStorePositionReason::WrongSlot(error.to_string())
1843        }
1844        error => HeldStorePositionReason::InvalidObject(error.to_string()),
1845    }
1846}
1847pub(crate) async fn load_commit_with_author(
1848    storage: &dyn SyncStorage,
1849    root: &StoreRootRef,
1850    reference: &StoreBatchCommitRef,
1851) -> Result<(StoreBatchCommit, StoreDeviceRegistration), StoreObjectError> {
1852    let semantic_prefix =
1853        super::store_commit::semantic_prefix_from_exact_object(&reference.object, ".json")
1854            .map_err(|source| StoreObjectError::InvalidObject {
1855                semantic_prefix: "Store candidate commit".to_string(),
1856                key: reference.object.slot().logical_key().to_string(),
1857                source: Box::new(source),
1858            })?;
1859    let context =
1860        ProtocolObjectContext::store(root.store_root_hash, ProtocolObjectDomain::StoreCommit);
1861    let bytes = storage
1862        .read_protocol_object(&context, &reference.object, &semantic_prefix)
1863        .await
1864        .map_err(StoreObjectError::Storage)?;
1865    let unverified: StoreBatchCommit =
1866        serde_json::from_slice(&bytes).map_err(|error| StoreObjectError::InvalidObject {
1867            semantic_prefix: semantic_prefix.clone(),
1868            key: reference.object.slot().logical_key().to_string(),
1869            source: Box::new(StoreProtocolError::Malformed(error.to_string())),
1870        })?;
1871    let author = load_registration_ref(storage, root, &unverified.author_registration)
1872        .await?
1873        .value;
1874    let commit =
1875        StoreBatchCommit::parse_at(&bytes, root.store_root_hash, &reference.coord, &author)
1876            .and_then(|commit| {
1877                reference.verify_commit(&commit)?;
1878                Ok(commit)
1879            })
1880            .map_err(|source| StoreObjectError::InvalidObject {
1881                semantic_prefix,
1882                key: reference.object.slot().logical_key().to_string(),
1883                source: Box::new(source),
1884            })?;
1885    Ok((commit, author))
1886}
1887
1888pub(crate) struct DeviceJoinBootstrapCommit {
1889    pub reference: StoreBatchCommitRef,
1890    pub commit: StoreBatchCommit,
1891    pub registrations: Vec<(StoreDeviceRegistration, StoreDeviceRegistrationActivation)>,
1892}
1893
1894pub(crate) struct DeviceJoinBootstrapPlan {
1895    pub founder_reference: StoreDeviceRegistrationRef,
1896    pub founder: StoreDeviceRegistration,
1897    pub founder_bytes: Vec<u8>,
1898    pub genesis: ResolvedStoreDeviceState,
1899    pub coverage: StoreHistoryCut,
1900    pub commits: Vec<DeviceJoinBootstrapCommit>,
1901}
1902
1903fn history_cut_references(cut: &StoreHistoryCut) -> Vec<StoreBatchCommitRef> {
1904    match cut {
1905        StoreHistoryCut::MergeConcurrent(frontier) => frontier.values().cloned().collect(),
1906        StoreHistoryCut::Serial(StoreSerialPredecessor::Commit(reference)) => {
1907            vec![reference.clone()]
1908        }
1909        StoreHistoryCut::Serial(StoreSerialPredecessor::Genesis { .. }) => Vec::new(),
1910    }
1911}
1912
1913fn commit_predecessor_references(commit: &StoreBatchCommit) -> Vec<StoreBatchCommitRef> {
1914    match &commit.order {
1915        super::store_commit::StoreCommitOrder::MergeConcurrent {
1916            predecessor,
1917            dependencies,
1918            ..
1919        } => predecessor
1920            .iter()
1921            .chain(dependencies.values())
1922            .cloned()
1923            .collect(),
1924        super::store_commit::StoreCommitOrder::Serial {
1925            predecessor: StoreSerialPredecessor::Commit(reference),
1926            ..
1927        } => vec![reference.clone()],
1928        super::store_commit::StoreCommitOrder::Serial {
1929            predecessor: StoreSerialPredecessor::Genesis { .. },
1930            ..
1931        } => Vec::new(),
1932    }
1933}
1934
1935pub(crate) async fn prepare_device_join_bootstrap(
1936    storage: &dyn SyncStorage,
1937    root: &StoreRootRef,
1938    coverage: &StoreHistoryCut,
1939    attempt_activation: &StoreBatchCommitRef,
1940    verified_authorization: &DeviceJoinBootstrapAuthorization,
1941) -> Result<DeviceJoinBootstrapPlan, StorePullError> {
1942    if coverage.policy() != attempt_activation.coord.policy() {
1943        return Err(StorePullError::Database(
1944            "device join bootstrap cut and attempt activation use different policies".to_string(),
1945        ));
1946    }
1947    let verified_root = Box::pin(load_store_protocol_root(storage, root))
1948        .await?
1949        .value;
1950    let founder = Box::pin(load_founder_registration(storage, root)).await?;
1951    let founder_reference =
1952        StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
1953    let genesis = ResolvedStoreDeviceState::founder(
1954        root,
1955        founder_reference.clone(),
1956        &verified_root.descriptor.founder_pubkey,
1957        verified_root.descriptor.founder_grant.clone(),
1958        &verified_root.descriptor.founder_recovery,
1959    )
1960    .map_err(|error| StorePullError::Database(error.to_string()))?;
1961
1962    let mut pending = history_cut_references(coverage);
1963    pending.push(attempt_activation.clone());
1964    let mut loaded =
1965        BTreeMap::<StoreBatchCommitRef, (StoreBatchCommit, StoreDeviceRegistration)>::new();
1966    while let Some(reference) = pending.pop() {
1967        if loaded.contains_key(&reference) {
1968            continue;
1969        }
1970        let (commit, author) = Box::pin(load_commit_with_author(storage, root, &reference)).await?;
1971        pending.extend(commit_predecessor_references(&commit));
1972        loaded.insert(reference, (commit, author));
1973    }
1974    let activation = loaded.get(attempt_activation).ok_or_else(|| {
1975        StorePullError::Database("device join attempt activation is absent from its graph".into())
1976    })?;
1977    if activation
1978        .0
1979        .order
1980        .predecessor_cut()
1981        .map_err(|error| StorePullError::Database(error.to_string()))?
1982        != *coverage
1983    {
1984        return Err(StorePullError::Database(
1985            "device join attempt activation predecessor differs from its signed bootstrap cut"
1986                .to_string(),
1987        ));
1988    }
1989
1990    let mut states = BTreeMap::<StoreBatchCommitRef, ResolvedStoreDeviceState>::new();
1991    let mut ordered = Vec::with_capacity(loaded.len());
1992    while !loaded.is_empty() {
1993        let next = loaded.iter().find_map(|(reference, (commit, _))| {
1994            commit_predecessor_references(commit)
1995                .iter()
1996                .all(|dependency| states.contains_key(dependency))
1997                .then(|| reference.clone())
1998        });
1999        let Some(reference) = next else {
2000            return Err(StorePullError::Database(
2001                "device join bootstrap history is cyclic or has an unresolved predecessor"
2002                    .to_string(),
2003            ));
2004        };
2005        let (commit, author) = loaded
2006            .remove(&reference)
2007            .expect("selected bootstrap commit remains loaded");
2008        let predecessor_state = match &commit.order {
2009            super::store_commit::StoreCommitOrder::MergeConcurrent {
2010                predecessor,
2011                dependencies,
2012                ..
2013            } => {
2014                let mut refs = dependencies.values().collect::<Vec<_>>();
2015                refs.extend(predecessor.iter());
2016                if refs.is_empty() {
2017                    genesis.clone()
2018                } else {
2019                    ResolvedStoreDeviceState::merge(refs.into_iter().map(|dependency| {
2020                        states
2021                            .get(dependency)
2022                            .expect("topological predecessor state exists")
2023                            .clone()
2024                    }))
2025                    .map_err(|error| StorePullError::Database(error.to_string()))?
2026                }
2027            }
2028            super::store_commit::StoreCommitOrder::Serial {
2029                predecessor: StoreSerialPredecessor::Genesis { .. },
2030                ..
2031            } => genesis.clone(),
2032            super::store_commit::StoreCommitOrder::Serial {
2033                predecessor: StoreSerialPredecessor::Commit(predecessor),
2034                ..
2035            } => states
2036                .get(predecessor)
2037                .expect("topological Serial predecessor state exists")
2038                .clone(),
2039        };
2040        let expected_state = match &commit.order {
2041            super::store_commit::StoreCommitOrder::MergeConcurrent {
2042                predecessor,
2043                dependencies,
2044                ..
2045            } => {
2046                let mut frontier = dependencies.clone();
2047                if let Some(predecessor) = predecessor {
2048                    let StoreCommitCoord::MergeConcurrent { stream_id, .. } = predecessor.coord
2049                    else {
2050                        return Err(StorePullError::Database(
2051                            "Merge bootstrap predecessor carries a Serial coordinate".to_string(),
2052                        ));
2053                    };
2054                    if frontier
2055                        .insert(stream_id, predecessor.clone())
2056                        .is_some_and(|existing| existing != *predecessor)
2057                    {
2058                        return Err(StorePullError::Database(
2059                            "Merge bootstrap predecessor conflicts with its dependency cut"
2060                                .to_string(),
2061                        ));
2062                    }
2063                }
2064                StoreDeviceStateRef::merge_concurrent(
2065                    CommitFrontier::MergeConcurrent(frontier),
2066                    &predecessor_state,
2067                )
2068            }
2069            super::store_commit::StoreCommitOrder::Serial { predecessor, .. } => {
2070                StoreDeviceStateRef::serial(predecessor.clone(), &predecessor_state)
2071            }
2072        }
2073        .map_err(|error| StorePullError::Database(error.to_string()))?;
2074        if commit.device_state != expected_state
2075            || !predecessor_state
2076                .devices
2077                .get(&commit.author_registration.device_id)
2078                .is_some_and(|record| {
2079                    record.registration == commit.author_registration
2080                        && matches!(
2081                            record.status,
2082                            super::store_commit::StoreDeviceStatus::Active
2083                        )
2084                })
2085        {
2086            return Err(StorePullError::Database(
2087                "device join bootstrap commit differs from its exact predecessor device state"
2088                    .to_string(),
2089            ));
2090        }
2091        let carries_lifecycle = !(commit.device_join_attempts().is_empty()
2092            && commit.device_join_outcomes().is_empty()
2093            && commit.device_join_abandonments().is_empty()
2094            && commit.device_join_cleanup_receipts().is_empty()
2095            && commit.device_registrations().is_empty());
2096        let verified_state = match verified_authorization {
2097            DeviceJoinBootstrapAuthorization::MergeConcurrent { state, .. }
2098            | DeviceJoinBootstrapAuthorization::Serial { state, .. } => state,
2099        };
2100        if carries_lifecycle && verified_state != &commit.membership_state {
2101            return Err(StorePullError::Database(
2102                "device join bootstrap history carries lifecycle authority outside the exact verified membership state"
2103                    .to_string(),
2104            ));
2105        }
2106        let authority = match verified_authorization {
2107            DeviceJoinBootstrapAuthorization::MergeConcurrent { chain, .. } => {
2108                RegistrationPredecessorAuthority::MergeConcurrent(chain)
2109            }
2110            DeviceJoinBootstrapAuthorization::Serial {
2111                position,
2112                authorization,
2113                ..
2114            } => RegistrationPredecessorAuthority::Serial {
2115                authorization,
2116                position: position.clone(),
2117            },
2118        };
2119        let registrations = Box::pin(load_commit_registrations(
2120            storage,
2121            root,
2122            &commit,
2123            &author,
2124            carries_lifecycle.then_some(&authority),
2125        ))
2126        .await
2127        .map_err(|error| match error {
2128            RegistrationLoadError::Object(error) => StorePullError::Object(error),
2129            RegistrationLoadError::Invalid(error) => StorePullError::Database(error),
2130        })?;
2131        let mut state = predecessor_state;
2132        for activation in commit.device_registrations() {
2133            state = state
2134                .activate_registration(activation.registration.clone(), None)
2135                .map_err(|error| StorePullError::Database(error.to_string()))?;
2136        }
2137        for retirement in commit.device_retirements() {
2138            state = state
2139                .self_retire(retirement.clone())
2140                .map_err(|error| StorePullError::Database(error.to_string()))?;
2141        }
2142        states.insert(reference.clone(), state);
2143        ordered.push(DeviceJoinBootstrapCommit {
2144            reference,
2145            commit,
2146            registrations,
2147        });
2148    }
2149
2150    Ok(DeviceJoinBootstrapPlan {
2151        founder_reference,
2152        founder: founder.value,
2153        founder_bytes: founder.bytes,
2154        genesis,
2155        coverage: coverage.clone(),
2156        commits: ordered,
2157    })
2158}
2159
2160pub(crate) async fn materialize_device_join_activation(
2161    db: &Database,
2162    storage: &dyn SyncStorage,
2163    root: &StoreRootRef,
2164    reference: &StoreBatchCommitRef,
2165    expected_outcome: &super::store_commit::DeviceJoinOutcomeRef,
2166    authorization: &DeviceJoinBootstrapAuthorization,
2167) -> Result<(), StorePullError> {
2168    let (stream_id, sequence) = match reference.coord {
2169        StoreCommitCoord::MergeConcurrent {
2170            stream_id,
2171            sequence,
2172        } => (stream_id.to_string(), sequence),
2173        StoreCommitCoord::Serial { sequence } => (SERIAL_STREAM_ID.to_string(), sequence),
2174    };
2175    if let Some(materialized) = db.exact_materialized_ref(&stream_id, sequence).await? {
2176        if materialized == *reference {
2177            return Ok(());
2178        }
2179        return Err(StorePullError::Database(format!(
2180            "device join activation coordinate {stream_id}/{sequence} is already occupied by another commit"
2181        )));
2182    }
2183    let (commit, author) = Box::pin(load_commit_with_author(storage, root, reference)).await?;
2184    if commit.device_join_outcomes() != std::slice::from_ref(expected_outcome)
2185        || !commit.device_join_attempts().is_empty()
2186        || !commit.device_join_abandonments().is_empty()
2187        || !commit.device_join_cleanup_receipts().is_empty()
2188        || commit.device_registrations().len() != 1
2189        || !commit.provider_access_grants().is_empty()
2190        || !commit.provider_access_withdrawals().is_empty()
2191        || !commit.device_retirements().is_empty()
2192        || !commit.circle_controls().is_empty()
2193        || !commit.circle_packages().is_empty()
2194        || commit.store_package().is_some()
2195        || commit.membership_authority.is_some()
2196        || commit.control().is_some()
2197    {
2198        return Err(StorePullError::Database(
2199            "device join activation commit carries unrelated operations".to_string(),
2200        ));
2201    }
2202    let authority = match authorization {
2203        DeviceJoinBootstrapAuthorization::MergeConcurrent { chain, .. } => {
2204            RegistrationPredecessorAuthority::MergeConcurrent(chain)
2205        }
2206        DeviceJoinBootstrapAuthorization::Serial {
2207            position,
2208            authorization,
2209            ..
2210        } => RegistrationPredecessorAuthority::Serial {
2211            authorization,
2212            position: position.clone(),
2213        },
2214    };
2215    let registrations = Box::pin(load_commit_registrations(
2216        storage,
2217        root,
2218        &commit,
2219        &author,
2220        Some(&authority),
2221    ))
2222    .await
2223    .map_err(|error| match error {
2224        RegistrationLoadError::Object(error) => StorePullError::Object(error),
2225        RegistrationLoadError::Invalid(error) => StorePullError::Database(error),
2226    })?;
2227    let author_authorized = match authorization {
2228        DeviceJoinBootstrapAuthorization::MergeConcurrent { chain, .. } => {
2229            membership_authorizes(Some(chain), &commit, &author)
2230        }
2231        DeviceJoinBootstrapAuthorization::Serial { authorization, .. } => {
2232            authorization.membership.can_write(&author.author_pubkey)
2233        }
2234    };
2235    if !author_authorized {
2236        return Err(StorePullError::Database(
2237            "device join activation author is not authorized by its exact predecessor membership"
2238                .to_string(),
2239        ));
2240    }
2241    let commit_ref = reference.clone();
2242    let expected_ref = reference.clone();
2243    db.call(move |connection| {
2244        let tx = connection
2245            .unchecked_transaction()
2246            .map_err(DbError::from)?;
2247        if let Some(materialized) =
2248            Database::materialized_commit_ref_on(&tx, &stream_id, sequence)?
2249        {
2250            if materialized != expected_ref {
2251                return Err(DbError::Message(format!(
2252                    "device join activation coordinate {stream_id}/{sequence} is already occupied by another commit"
2253                )));
2254            }
2255            tx.commit().map_err(DbError::from)?;
2256            return Ok(());
2257        }
2258        Database::record_activated_store_device_registrations_on(
2259            &tx,
2260            &commit,
2261            &registrations,
2262        )?;
2263        Database::record_materialized_commit_on(&tx, &commit, &commit_ref)?;
2264        tx.commit().map_err(DbError::from)
2265    })
2266    .await?;
2267    Ok(())
2268}
2269
2270async fn load_authorized_serial_prefix(
2271    storage: &dyn SyncStorage,
2272    root: &StoreRootRef,
2273    tip: Option<StoreBatchCommitRef>,
2274) -> Result<(Vec<AuthorizedSerialCommit>, SerialAuthorizationState), StorePullError> {
2275    let root_value = load_store_protocol_root(storage, root).await?.value;
2276    if root_value.descriptor.write_policy != crate::WritePolicy::Serial {
2277        return Err(StorePullError::Serial(format!(
2278            "Store protocol root uses {:?}, not Serial",
2279            root_value.descriptor.write_policy
2280        )));
2281    }
2282
2283    let mut expected = tip;
2284    let mut reverse = Vec::new();
2285    while let Some(reference) = expected {
2286        if !matches!(reference.coord, StoreCommitCoord::Serial { .. }) {
2287            return Err(StorePullError::Serial(
2288                "global predecessor chain contains a Merge commit reference".to_string(),
2289            ));
2290        }
2291        let (commit, author) = load_commit_with_author(storage, root, &reference).await?;
2292        expected = commit.order.predecessor().cloned();
2293        reverse.push((reference, commit, author));
2294    }
2295    reverse.reverse();
2296
2297    let founder = load_founder_registration(storage, root).await?;
2298    let founder_ref =
2299        StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
2300    let mut authorization =
2301        SerialAuthorizationState::from_founder(root, &root_value, &founder_ref, &founder.value)
2302            .map_err(|error| StorePullError::Serial(error.to_string()))?;
2303    let mut active = BTreeMap::new();
2304    let mut predecessor = None;
2305    let mut authorized = Vec::with_capacity(reverse.len());
2306
2307    for (reference, commit, author) in reverse {
2308        match (&predecessor, &commit.order) {
2309            (
2310                None,
2311                super::store_commit::StoreCommitOrder::Serial {
2312                    seq: 1,
2313                    predecessor:
2314                        StoreSerialPredecessor::Genesis {
2315                            root: genesis_root,
2316                            founder_registration,
2317                        },
2318                },
2319            ) if genesis_root == root && founder_registration == &founder_ref => {
2320                let recovery_author =
2321                    commit
2322                        .serial_recovery_activation()
2323                        .as_ref()
2324                        .is_some_and(|activation| {
2325                            activation.registration.registration == commit.author_registration
2326                        });
2327                if founder.value.author_pubkey != root_value.descriptor.founder_pubkey
2328                    || (!recovery_author && founder_registration != &commit.author_registration)
2329                {
2330                    return Err(StorePullError::Serial(
2331                        "Serial genesis registration is not the Store founder".to_string(),
2332                    ));
2333                }
2334                active.insert(founder_registration.device_id, founder_registration.clone());
2335            }
2336            (
2337                Some(previous),
2338                super::store_commit::StoreCommitOrder::Serial {
2339                    seq,
2340                    predecessor: StoreSerialPredecessor::Commit(declared),
2341                },
2342            ) if declared == previous
2343                && *seq
2344                    == previous.coord.sequence().checked_add(1).ok_or_else(|| {
2345                        StorePullError::Serial("Serial predecessor sequence overflow".to_string())
2346                    })? => {}
2347            _ => {
2348                return Err(StorePullError::Serial(format!(
2349                    "Serial commit {} does not extend the exact accepted predecessor",
2350                    reference.coord.sequence()
2351                )));
2352            }
2353        }
2354
2355        let recovery_author =
2356            commit
2357                .serial_recovery_activation()
2358                .as_ref()
2359                .is_some_and(|activation| {
2360                    activation.registration.registration == commit.author_registration
2361                });
2362        if active.get(&commit.author_registration.device_id) != Some(&commit.author_registration)
2363            && !recovery_author
2364        {
2365            return Err(StorePullError::Serial(format!(
2366                "Serial commit {} author registration is not active at its predecessor",
2367                reference.coord.sequence()
2368            )));
2369        }
2370        if author.device_id != commit.author_registration.device_id {
2371            return Err(StorePullError::Serial(
2372                "Serial commit author bytes differ from its exact registration".to_string(),
2373            ));
2374        }
2375        let predecessor_authority = RegistrationPredecessorAuthority::Serial {
2376            authorization: &authorization,
2377            position: match &commit.order {
2378                super::store_commit::StoreCommitOrder::Serial { predecessor, .. } => {
2379                    predecessor.clone()
2380                }
2381                super::store_commit::StoreCommitOrder::MergeConcurrent { .. } => {
2382                    return Err(StorePullError::Serial(
2383                        "Serial chain contains a Merge commit order".to_string(),
2384                    ));
2385                }
2386            },
2387        };
2388        let registrations = load_commit_registrations(
2389            storage,
2390            root,
2391            &commit,
2392            &author,
2393            Some(&predecessor_authority),
2394        )
2395        .await
2396        .map_err(|error| match error {
2397            RegistrationLoadError::Object(error) => StorePullError::Object(error),
2398            RegistrationLoadError::Invalid(error) => StorePullError::Serial(error),
2399        })?;
2400        validate_serial_provider_admin_control(storage, root, &root_value, commit.control())
2401            .await?;
2402        authorization = authorization
2403            .authorize_and_apply(&reference, &commit, &author)
2404            .map_err(|error| {
2405                StorePullError::Serial(format!(
2406                    "commit {} authorization: {error}",
2407                    reference.coord.sequence()
2408                ))
2409            })?;
2410        for activated in commit.device_registrations() {
2411            active.insert(
2412                activated.registration.device_id,
2413                activated.registration.clone(),
2414            );
2415        }
2416        for retirement in commit.device_retirements() {
2417            active.remove(&retirement.target.device_id);
2418        }
2419        predecessor = Some(reference.clone());
2420        authorized.push(AuthorizedSerialCommit {
2421            commit_ref: reference,
2422            commit,
2423            author,
2424            registrations,
2425            authorization_after: authorization.clone(),
2426        });
2427    }
2428    Ok((authorized, authorization))
2429}
2430
2431pub(crate) async fn validate_serial_provider_admin_control(
2432    storage: &dyn SyncStorage,
2433    root: &StoreRootRef,
2434    root_value: &super::store_commit::StoreProtocolRoot,
2435    control: Option<&super::store_commit::StoreControl>,
2436) -> Result<(), StorePullError> {
2437    let Some(super::store_commit::StoreControl::ProviderAdmin {
2438        change:
2439            super::provider::ProviderAdminChange::Set {
2440                administrator,
2441                provider,
2442                capability,
2443                ..
2444            },
2445    }) = control
2446    else {
2447        return Ok(());
2448    };
2449    let registration =
2450        super::store_objects::load_registration_ref(storage, root, administrator).await?;
2451    if registration.value.store_root != *root || registration.value.provider != *provider {
2452        return Err(StorePullError::Serial(
2453            "provider administrator grant does not match its exact device registration".to_string(),
2454        ));
2455    }
2456    capability
2457        .verify(&root_value.descriptor.provider, provider, true)
2458        .map_err(|error| StorePullError::Serial(error.to_string()))
2459}
2460
2461async fn load_authorized_serial_chain(
2462    storage: &dyn SyncStorage,
2463    root: &StoreRootRef,
2464    head: &StoreSerialHead,
2465) -> Result<Vec<AuthorizedSerialCommit>, StorePullError> {
2466    let tip = match &head.state {
2467        StoreSerialHeadState::Genesis {
2468            root: head_root, ..
2469        } => {
2470            if head_root != root {
2471                return Err(StorePullError::Serial(
2472                    "Serial genesis head names another exact Store root".to_string(),
2473                ));
2474            }
2475            None
2476        }
2477        StoreSerialHeadState::Commit { commit, .. } => Some(commit.clone()),
2478    };
2479    let (authorized, _) = load_authorized_serial_prefix(storage, root, tip.clone()).await?;
2480    match (&head.state, authorized.last()) {
2481        (StoreSerialHeadState::Genesis { .. }, None) => {}
2482        (
2483            StoreSerialHeadState::Commit {
2484                author_registration,
2485                commit,
2486            },
2487            Some(accepted),
2488        ) if commit == &accepted.commit_ref
2489            && author_registration == &accepted.commit.author_registration => {}
2490        _ => {
2491            return Err(StorePullError::Serial(
2492                "signed global head is not bound to its exact tip commit".to_string(),
2493            ));
2494        }
2495    }
2496    Ok(authorized)
2497}
2498
2499struct VerifiedSerialHead {
2500    head: StoreSerialHead,
2501    object: super::storage::VersionedObject,
2502}
2503
2504async fn read_serial_head(
2505    storage: &dyn SyncStorage,
2506    coordination: &dyn CoordinationStorage,
2507    root: &StoreRootRef,
2508) -> Result<VerifiedSerialHead, StorePullError> {
2509    let object = match coordination.read_head(serial_head_key()).await {
2510        Ok(object) => object,
2511        Err(CoordinationError::NotFound(_)) => {
2512            return Err(StorePullError::Serial("global head is absent".to_string()));
2513        }
2514        Err(error) => return Err(StorePullError::Coordination(error)),
2515    };
2516    let unverified: StoreSerialHead = serde_json::from_slice(&object.bytes)
2517        .map_err(|error| StorePullError::Serial(format!("invalid head: {error}")))?;
2518    let executor_ref = match &unverified.state {
2519        StoreSerialHeadState::Genesis {
2520            founder_registration,
2521            ..
2522        } => founder_registration,
2523        StoreSerialHeadState::Commit {
2524            author_registration,
2525            ..
2526        } => author_registration,
2527    };
2528    let executor = load_registration_ref(storage, root, executor_ref)
2529        .await?
2530        .value;
2531    let head = StoreSerialHead::parse(&object.bytes, root.store_root_hash, &executor)
2532        .map_err(|error| StorePullError::Serial(format!("invalid head: {error}")))?;
2533    Ok(VerifiedSerialHead { head, object })
2534}
2535
2536pub(crate) async fn load_serial_authorization_at_head(
2537    storage: &dyn SyncStorage,
2538    root: &StoreRootRef,
2539    head: &StoreSerialHead,
2540) -> Result<SerialAuthorizationState, StorePullError> {
2541    let authorized = load_authorized_serial_chain(storage, root, head).await?;
2542    match authorized.last() {
2543        Some(tip) => Ok(tip.authorization_after.clone()),
2544        None => load_serial_authorization_at_position(storage, root, None).await,
2545    }
2546}
2547
2548pub(crate) struct SerialCycleAuthorization {
2549    pub authorization: SerialAuthorizationState,
2550    pub head: Option<StoreBatchCommitRef>,
2551    pub visible_activations: Vec<super::wrapped_store_key::WrappedKeyActivation>,
2552}
2553
2554pub(crate) async fn load_serial_cycle_authorization(
2555    storage: &dyn SyncStorage,
2556    coordination: &dyn CoordinationStorage,
2557    root: &StoreRootRef,
2558) -> Result<SerialCycleAuthorization, StorePullError> {
2559    let head = read_serial_head(storage, coordination, root).await?.head;
2560    let authorized = load_authorized_serial_chain(storage, root, &head).await?;
2561    let authorization = match authorized.last() {
2562        Some(tip) => tip.authorization_after.clone(),
2563        None => load_serial_authorization_at_position(storage, root, None).await?,
2564    };
2565    let visible_activations = authorized
2566        .iter()
2567        .map(|commit| {
2568            super::wrapped_store_key::WrappedKeyActivation::Serial(commit.commit_ref.clone())
2569        })
2570        .collect();
2571    let head = match head.state {
2572        StoreSerialHeadState::Genesis { .. } => None,
2573        StoreSerialHeadState::Commit { commit, .. } => Some(commit),
2574    };
2575    Ok(SerialCycleAuthorization {
2576        authorization,
2577        head,
2578        visible_activations,
2579    })
2580}
2581
2582pub async fn load_serial_authorization_at_position(
2583    storage: &dyn SyncStorage,
2584    root: &StoreRootRef,
2585    reference: Option<StoreBatchCommitRef>,
2586) -> Result<SerialAuthorizationState, StorePullError> {
2587    let (_, authorization) = load_authorized_serial_prefix(storage, root, reference).await?;
2588    Ok(authorization)
2589}
2590
2591pub(crate) async fn load_serial_snapshot_authorities_at_position(
2592    storage: &dyn SyncStorage,
2593    root: &StoreRootRef,
2594    reference: Option<StoreBatchCommitRef>,
2595) -> Result<Vec<(StoreDeviceRegistrationRef, StoreDeviceRegistration)>, StorePullError> {
2596    let (authorized, authorization) =
2597        load_authorized_serial_prefix(storage, root, reference).await?;
2598    let founder = load_founder_registration(storage, root).await?;
2599    let founder_ref =
2600        StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
2601    let mut active = BTreeMap::from([(founder_ref, founder.value)]);
2602    for accepted in authorized {
2603        for (activated, (registration, _)) in accepted
2604            .commit
2605            .device_registrations()
2606            .iter()
2607            .zip(accepted.registrations)
2608        {
2609            active.insert(activated.registration.clone(), registration);
2610        }
2611        for retirement in accepted.commit.device_retirements() {
2612            active.remove(&retirement.target);
2613        }
2614    }
2615    Ok(active
2616        .into_iter()
2617        .filter(|(_, registration)| {
2618            authorization
2619                .membership
2620                .is_owner(&registration.author_pubkey)
2621        })
2622        .collect())
2623}
2624
2625async fn pull_serial_store_commits(
2626    db: &Database,
2627    tables: &[SyncedTable],
2628    storage: &dyn SyncStorage,
2629    coordination: &dyn CoordinationStorage,
2630    root: &StoreRootRef,
2631    root_value: super::store_commit::StoreProtocolRoot,
2632    store_dir: &StoreDir,
2633    identity: Option<&crate::keys::UserKeypair>,
2634) -> Result<StorePullResult, StorePullError> {
2635    if root_value.descriptor.write_policy != crate::WritePolicy::Serial {
2636        return Err(StorePullError::Serial(
2637            "signed Store root is not Serial".to_string(),
2638        ));
2639    }
2640    let local = db.materialized_frontier().await?.remove(SERIAL_STREAM_ID);
2641    let head = read_serial_head(storage, coordination, root).await?.head;
2642    let authorized_chain = load_authorized_serial_chain(storage, root, &head).await?;
2643    let tip = match &head.state {
2644        StoreSerialHeadState::Genesis { .. } => None,
2645        StoreSerialHeadState::Commit { commit, .. } => Some(commit.clone()),
2646    };
2647    let Some(tip) = tip else {
2648        if local.is_some() {
2649            return Err(StorePullError::Serial(format!(
2650                "global head is genesis but the durable Serial frontier is {local:?}"
2651            )));
2652        }
2653        return empty_serial_pull_result(db, store_dir, Some(head)).await;
2654    };
2655    if local
2656        .as_ref()
2657        .is_some_and(|local| local.coord.sequence() > tip.coord.sequence())
2658    {
2659        return Err(StorePullError::Serial(format!(
2660            "local Serial reference is ahead of the signed head: local={local:?}, head={tip:?}"
2661        )));
2662    }
2663
2664    let first_unmaterialized = match local.as_ref() {
2665        None => 0,
2666        Some(local) => authorized_chain
2667            .iter()
2668            .position(|authorized| &authorized.commit_ref == local)
2669            .map(|index| index + 1)
2670            .ok_or_else(|| {
2671                StorePullError::Serial(format!(
2672                    "exact Serial predecessor chain does not reach local reference {local:?}"
2673                ))
2674            })?,
2675    };
2676    if let Some(local) = local.as_ref() {
2677        let authorization = authorized_chain
2678            .get(first_unmaterialized - 1)
2679            .expect("materialized Serial reference was found in the authorized chain")
2680            .authorization_after
2681            .clone();
2682        db.install_serial_authorization_at_position(local.clone(), authorization)
2683            .await?;
2684    }
2685
2686    let mut candidates = Vec::with_capacity(authorized_chain.len() - first_unmaterialized);
2687    for authorized in authorized_chain.into_iter().skip(first_unmaterialized) {
2688        let package =
2689            load_serial_store_package(db, storage, &authorized.commit_ref, &authorized.commit)
2690                .await?;
2691        let circle_activations = match load_pull_circle_activations(
2692            db,
2693            storage,
2694            root,
2695            &authorized.commit_ref,
2696            &authorized.commit,
2697            &authorized.author,
2698            identity,
2699        )
2700        .await
2701        {
2702            Ok(activations) => activations,
2703            Err(PullCircleActivationError::Database(error)) => return Err(error.into()),
2704            Err(PullCircleActivationError::Invalid(error)) => {
2705                return Err(StorePullError::Serial(error));
2706            }
2707        };
2708        candidates.push((
2709            Candidate {
2710                commit_ref: authorized.commit_ref,
2711                commit: authorized.commit,
2712                author: authorized.author,
2713                package,
2714                registrations: authorized.registrations,
2715                circle_activations,
2716            },
2717            authorized.authorization_after,
2718        ));
2719    }
2720
2721    let schema: Arc<TableSchema> = {
2722        let tables = tables.to_vec();
2723        Arc::new(
2724            db.call(move |conn| TableSchema::from_db(conn, &tables))
2725                .await?,
2726        )
2727    };
2728    let mut row_changes = Vec::new();
2729    let mut authors = BTreeSet::new();
2730    let mut applied_candidates = 0_u64;
2731    for (candidate, authorization_after) in &candidates {
2732        let changes = match apply_serial_candidate(
2733            db,
2734            storage,
2735            store_dir,
2736            schema.clone(),
2737            candidate,
2738            authorization_after,
2739        )
2740        .await
2741        {
2742            Ok(changes) => changes,
2743            Err(StorePullError::BlobDownloads(failures)) if !failures.has_transport_failure() => {
2744                tracing::warn!(
2745                    stream_id = %commit_stream_id(&candidate.commit_ref.coord),
2746                    seq = candidate.commit_ref.coord.sequence(),
2747                    %failures,
2748                    "holding Serial commit on blob download failure"
2749                );
2750                let frontier = db.materialized_frontier().await?;
2751                let local_blob_cleanup_pending = local_cleanup::drain(db, store_dir).await?;
2752                return Ok(StorePullResult {
2753                    changesets_applied: applied_candidates,
2754                    devices_pulled: u64::try_from(authors.len()).map_err(|_| {
2755                        StorePullError::Serial("author count exceeds u64".to_string())
2756                    })?,
2757                    held_positions: vec![held_commit(
2758                        &candidate.commit_ref,
2759                        HeldStorePositionReason::BlobDownloadFailed,
2760                    )],
2761                    visible_heads: Vec::new(),
2762                    serial_head: Some(head),
2763                    row_changes,
2764                    asset_downloads_failed: true,
2765                    local_blob_cleanup_pending,
2766                    frontier,
2767                });
2768            }
2769            Err(error) => return Err(error),
2770        };
2771        authors.insert(candidate.author.device_id);
2772        row_changes.extend(changes);
2773        applied_candidates = applied_candidates
2774            .checked_add(1)
2775            .ok_or_else(|| StorePullError::Serial("apply count exceeds u64".to_string()))?;
2776    }
2777    let frontier = db.materialized_frontier().await?;
2778    let local_blob_cleanup_pending = local_cleanup::drain(db, store_dir).await?;
2779    Ok(StorePullResult {
2780        changesets_applied: applied_candidates,
2781        devices_pulled: u64::try_from(authors.len())
2782            .map_err(|_| StorePullError::Serial("author count exceeds u64".to_string()))?,
2783        held_positions: Vec::new(),
2784        visible_heads: Vec::new(),
2785        serial_head: Some(head),
2786        row_changes,
2787        asset_downloads_failed: false,
2788        local_blob_cleanup_pending,
2789        frontier,
2790    })
2791}
2792
2793async fn empty_serial_pull_result(
2794    db: &Database,
2795    store_dir: &StoreDir,
2796    serial_head: Option<StoreSerialHead>,
2797) -> Result<StorePullResult, StorePullError> {
2798    let frontier = db.materialized_frontier().await?;
2799    let local_blob_cleanup_pending = local_cleanup::drain(db, store_dir).await?;
2800    Ok(StorePullResult {
2801        changesets_applied: 0,
2802        devices_pulled: 0,
2803        held_positions: Vec::new(),
2804        visible_heads: Vec::new(),
2805        serial_head,
2806        row_changes: Vec::new(),
2807        asset_downloads_failed: false,
2808        local_blob_cleanup_pending,
2809        frontier,
2810    })
2811}
2812
2813#[doc(hidden)]
2814pub async fn prepare_serial_resolution(
2815    db: &Database,
2816    storage: &dyn SyncStorage,
2817    coordination: &dyn CoordinationStorage,
2818    store_root_hash: ObjectHash,
2819    store_dir: &StoreDir,
2820    branch_base: Option<StoreBatchCommitRef>,
2821    identity: &crate::keys::UserKeypair,
2822) -> Result<SerialResolutionPlan, StorePullError> {
2823    let root = db.local_store_root_ref().await?.ok_or_else(|| {
2824        StorePullError::Serial("Store root exact reference is absent".to_string())
2825    })?;
2826    if root.store_root_hash != store_root_hash {
2827        return Err(StorePullError::Serial(
2828            "Serial resolution root differs from durable exact root".to_string(),
2829        ));
2830    }
2831    let verified_head = read_serial_head(storage, coordination, &root).await?;
2832    let head = verified_head.head;
2833    let authorized_chain = load_authorized_serial_chain(storage, &root, &head).await?;
2834    let first = match branch_base.as_ref() {
2835        None => 0,
2836        Some(base) => authorized_chain
2837            .iter()
2838            .position(|authorized| &authorized.commit_ref == base)
2839            .map(|index| index + 1)
2840            .ok_or_else(|| {
2841                StorePullError::Serial(
2842                    "global chain does not descend from the exact conflicting branch base"
2843                        .to_string(),
2844                )
2845            })?,
2846    };
2847    let schema: Arc<TableSchema> = {
2848        let tables = db.synced_tables().to_vec();
2849        Arc::new(
2850            db.call(move |conn| TableSchema::from_db(conn, &tables))
2851                .await?,
2852        )
2853    };
2854    let mut commits = Vec::with_capacity(authorized_chain.len() - first);
2855    for authorized in authorized_chain.into_iter().skip(first) {
2856        let package =
2857            load_serial_store_package(db, storage, &authorized.commit_ref, &authorized.commit)
2858                .await?;
2859        let circle_activations = match load_pull_circle_activations(
2860            db,
2861            storage,
2862            &root,
2863            &authorized.commit_ref,
2864            &authorized.commit,
2865            &authorized.author,
2866            Some(identity),
2867        )
2868        .await
2869        {
2870            Ok(activations) => activations,
2871            Err(PullCircleActivationError::Database(error)) => return Err(error.into()),
2872            Err(PullCircleActivationError::Invalid(error)) => {
2873                return Err(StorePullError::Serial(error));
2874            }
2875        };
2876        let candidate = Candidate {
2877            commit_ref: authorized.commit_ref.clone(),
2878            commit: authorized.commit,
2879            author: authorized.author,
2880            package,
2881            registrations: authorized.registrations,
2882            circle_activations,
2883        };
2884        let cleanup = if candidate.package.is_some() {
2885            prepare_serial_candidate(db, storage, store_dir, schema.clone(), &candidate)
2886                .await?
2887                .cleanup
2888        } else {
2889            Vec::new()
2890        };
2891        commits.push(SerialResolutionCommit {
2892            commit: candidate.commit,
2893            commit_ref: candidate.commit_ref,
2894            package: candidate.package,
2895            cleanup,
2896            registrations: candidate.registrations,
2897            circle_activations: candidate.circle_activations,
2898            authorization_after: authorized.authorization_after,
2899        });
2900    }
2901    Ok(SerialResolutionPlan {
2902        head,
2903        head_object: verified_head.object,
2904        commits,
2905    })
2906}
2907
2908#[doc(hidden)]
2909pub async fn cleanup_serial_candidates(
2910    db: &Database,
2911    storage: &dyn SyncStorage,
2912    branch_id: crate::PendingBranchId,
2913    plan: &SerialResolutionPlan,
2914) -> Result<(), StorePullError> {
2915    let targets = db.prepare_serial_candidate_cleanup(branch_id, plan).await?;
2916    for target in targets {
2917        super::store_objects::delete_exact_object(storage, &target.object).await?;
2918        db.mark_candidate_cleanup_absent(target.object).await?;
2919    }
2920    Ok(())
2921}
2922
2923pub async fn cleanup_serial_abandonment_authority(
2924    db: &Database,
2925    storage: &dyn SyncStorage,
2926    plan: &SerialResolutionPlan,
2927) -> Result<(), StorePullError> {
2928    let target = db
2929        .prepare_serial_abandonment_authority_cleanup(plan)
2930        .await?;
2931    if let Some(target) = target {
2932        super::store_objects::delete_exact_object(storage, &target.object).await?;
2933        db.mark_candidate_cleanup_absent(target.object).await?;
2934    }
2935    Ok(())
2936}
2937
2938pub async fn cleanup_merge_candidate(
2939    db: &Database,
2940    storage: &dyn SyncStorage,
2941    write_id: crate::WriteId,
2942) -> Result<(), StorePullError> {
2943    let targets = db.merge_candidate_cleanup_targets(write_id).await?;
2944    for target in targets {
2945        super::store_objects::delete_exact_object(storage, &target.object).await?;
2946        db.mark_candidate_cleanup_absent(target.object).await?;
2947    }
2948    Ok(())
2949}
2950
2951async fn apply_serial_candidate(
2952    db: &Database,
2953    storage: &dyn SyncStorage,
2954    store_dir: &StoreDir,
2955    schema: Arc<TableSchema>,
2956    candidate: &Candidate,
2957    authorization_after: &SerialAuthorizationState,
2958) -> Result<Vec<RowChange>, StorePullError> {
2959    if candidate.package.is_none() {
2960        let commit = candidate.commit.clone();
2961        let commit_ref = candidate.commit_ref.clone();
2962        let registrations = candidate.registrations.clone();
2963        let circle_activations = candidate.circle_activations.clone();
2964        let authorization_after = authorization_after.clone();
2965        db.call(move |conn| {
2966            let tx = conn.unchecked_transaction().map_err(DbError::from)?;
2967            Database::record_activated_store_device_registrations_on(&tx, &commit, &registrations)?;
2968            Database::record_verified_circle_activations_on(
2969                &tx,
2970                &commit,
2971                &commit_ref,
2972                &circle_activations,
2973            )?;
2974            Database::record_materialized_serial_commit_on(
2975                &tx,
2976                &commit,
2977                &commit_ref,
2978                &authorization_after,
2979            )?;
2980            tx.commit().map_err(DbError::from)
2981        })
2982        .await?;
2983        return Ok(Vec::new());
2984    }
2985
2986    let prepared = prepare_serial_candidate(db, storage, store_dir, schema, candidate).await?;
2987    let PreparedSerialCandidate {
2988        changeset,
2989        changes,
2990        cleanup,
2991    } = prepared;
2992    let commit = candidate.commit.clone();
2993    let commit_ref = candidate.commit_ref.clone();
2994    let registrations = candidate.registrations.clone();
2995    let circle_activations = candidate.circle_activations.clone();
2996    let authorization_after = authorization_after.clone();
2997    let returned_changes = changes.clone();
2998    let blob_decls = db.blob_decls();
2999    let receiver_wall_ms = db.receive_wall_ms();
3000    let mut changeset_max = None;
3001    advance_max_updated_at(
3002        &mut changeset_max,
3003        &changes,
3004        changeset.schema(),
3005        receiver_wall_ms,
3006    );
3007    let hlc = db.hlc();
3008    db.call(move |conn| {
3009        let tx = conn.unchecked_transaction().map_err(DbError::from)?;
3010        apply_changeset_strict_on(&tx, changeset).map_err(|error| {
3011            DbError::Message(format!(
3012                "Serial commit {} did not apply exactly: {error}",
3013                commit_ref.coord.sequence()
3014            ))
3015        })?;
3016        for intent in cleanup {
3017            local_cleanup::record_if_unreferenced_on(&tx, &blob_decls, &intent)?;
3018        }
3019        Database::record_activated_store_device_registrations_on(&tx, &commit, &registrations)?;
3020        Database::record_verified_circle_activations_on(
3021            &tx,
3022            &commit,
3023            &commit_ref,
3024            &circle_activations,
3025        )?;
3026        Database::record_materialized_serial_commit_on(
3027            &tx,
3028            &commit,
3029            &commit_ref,
3030            &authorization_after,
3031        )?;
3032        tx.commit().map_err(DbError::from)?;
3033        if let Some(max_applied) = changeset_max.as_ref() {
3034            hlc.advance_past(max_applied);
3035        }
3036        Ok(())
3037    })
3038    .await?;
3039    Ok(returned_changes)
3040}
3041
3042struct PreparedSerialCandidate {
3043    changeset: ValidatedChangeset<Vec<u8>>,
3044    changes: Vec<RowChange>,
3045    cleanup: Vec<LocalBlobCleanupIntent>,
3046}
3047
3048async fn prepare_serial_candidate(
3049    db: &Database,
3050    storage: &dyn SyncStorage,
3051    store_dir: &StoreDir,
3052    schema: Arc<TableSchema>,
3053    candidate: &Candidate,
3054) -> Result<PreparedSerialCandidate, StorePullError> {
3055    let package_bytes = candidate.package.as_ref().ok_or_else(|| {
3056        StorePullError::Serial("row preparation requires a Store package".to_string())
3057    })?;
3058    let package =
3059        parse_candidate_store_package(candidate, package_bytes).map_err(StorePullError::Serial)?;
3060    let changeset = ValidatedChangeset::new(package.changeset().to_vec(), schema)
3061        .map_err(|error| StorePullError::Serial(format!("invalid changeset: {error}")))?;
3062    let changes = crate::changeset::walk(changeset.bytes())
3063        .map_err(|error| StorePullError::Serial(format!("invalid changeset: {error}")))?;
3064    let old_changes = crate::changeset::walk_old(changeset.bytes())
3065        .map_err(|error| StorePullError::Serial(format!("invalid changeset: {error}")))?;
3066    let blob_decls = db.blob_decls();
3067    let eager = cache_eager_blobs(&blob_decls, &changes, &package)
3068        .map_err(|error| StorePullError::Serial(format!("invalid blob changes: {error}")))?;
3069    if let Err(failures) = download_blobs(db, eager, storage, store_dir).await {
3070        return Err(StorePullError::BlobDownloads(failures));
3071    }
3072    let cleanup = local_blob_cleanup_intents(&blob_decls, &old_changes, &changes)
3073        .map_err(|error| StorePullError::Serial(format!("invalid blob changes: {error}")))?;
3074    Ok(PreparedSerialCandidate {
3075        changeset,
3076        changes,
3077        cleanup,
3078    })
3079}
3080fn membership_authorizes(
3081    membership: Option<&MembershipChain>,
3082    commit: &StoreBatchCommit,
3083    author: &StoreDeviceRegistration,
3084) -> bool {
3085    let Some(chain) = membership else {
3086        return true;
3087    };
3088    let Some(authority) = commit.membership_authority.as_ref() else {
3089        return chain.contains_member_now(&author.author_pubkey);
3090    };
3091    chain.authorizes_write_authority(authority, &author.author_pubkey)
3092}
3093
3094fn carries_circle_payload(commit: &StoreBatchCommit) -> bool {
3095    !commit.circle_controls().is_empty() || !commit.circle_packages().is_empty()
3096}
3097
3098enum PullCircleActivationError {
3099    Database(DbError),
3100    Invalid(String),
3101}
3102
3103async fn load_pull_circle_activations(
3104    db: &Database,
3105    storage: &dyn SyncStorage,
3106    root: &StoreRootRef,
3107    commit_ref: &StoreBatchCommitRef,
3108    commit: &StoreBatchCommit,
3109    author: &StoreDeviceRegistration,
3110    identity: Option<&crate::keys::UserKeypair>,
3111) -> Result<Vec<super::circle_ops::VerifiedCircleReference>, PullCircleActivationError> {
3112    if !carries_circle_payload(commit) {
3113        return Ok(Vec::new());
3114    }
3115    if !commit.circle_packages().is_empty() {
3116        return Err(PullCircleActivationError::Invalid(
3117            "circle row packages are not implemented".to_string(),
3118        ));
3119    }
3120    let identity = identity.ok_or_else(|| {
3121        PullCircleActivationError::Invalid(format!(
3122            "commit {} carries circle controls but no device identity was supplied",
3123            commit.seq()
3124        ))
3125    })?;
3126    let founder = db
3127        .get_protocol_state(super::membership_ops::OWNER_PUBKEY_STATE_KEY)
3128        .await
3129        .map_err(PullCircleActivationError::Database)?
3130        .ok_or_else(|| {
3131            PullCircleActivationError::Invalid(
3132                "Store founder is absent while loading circle controls".to_string(),
3133            )
3134        })?;
3135    super::circle_ops::load_circle_activations(
3136        storage, root, commit_ref, commit, author, identity, &founder,
3137    )
3138    .await
3139    .map_err(|error| PullCircleActivationError::Invalid(error.to_string()))
3140}
3141
3142async fn load_serial_store_package(
3143    db: &Database,
3144    storage: &dyn SyncStorage,
3145    commit_ref: &StoreBatchCommitRef,
3146    commit: &StoreBatchCommit,
3147) -> Result<Option<Vec<u8>>, StorePullError> {
3148    if let Some(package) = commit.store_package() {
3149        if package.schema_version > db.schema_version() {
3150            return Err(StorePullError::Serial(format!(
3151                "commit {} requires schema {}, local schema is {}",
3152                commit.seq(),
3153                package.schema_version,
3154                db.schema_version()
3155            )));
3156        }
3157    }
3158    match load_store_package(storage, commit_ref, commit).await? {
3159        Some(package) => Ok(Some(package.value)),
3160        None if commit.store_package().is_none() => Ok(None),
3161        None => Err(StorePullError::Serial(format!(
3162            "commit {} Store package is absent",
3163            commit.seq()
3164        ))),
3165    }
3166}
3167
3168enum Readiness {
3169    Ready,
3170    AlreadyMaterialized,
3171    Held(HeldStorePosition),
3172}
3173
3174enum MaterializedCheck {
3175    Yes,
3176    Missing,
3177    Held(HeldStorePositionReason),
3178}
3179
3180fn held_object_error(error: StoreObjectError) -> HeldStorePositionReason {
3181    match error {
3182        StoreObjectError::Storage(source) => HeldStorePositionReason::ObjectUnreadable {
3183            key: "exact Store object".to_string(),
3184            detail: source.to_string(),
3185        },
3186        StoreObjectError::InvalidObject { key, source, .. } => match *source {
3187            StoreProtocolError::InvalidSignature => HeldStorePositionReason::InvalidSignature,
3188            StoreProtocolError::RelocatedSlot { .. }
3189            | StoreProtocolError::RelocatedPackage { .. }
3190            | StoreProtocolError::StoreRootMismatch { .. }
3191            | StoreProtocolError::StoreMismatch { .. }
3192            | StoreProtocolError::FounderMismatch { .. } => {
3193                HeldStorePositionReason::WrongSlot(source.to_string())
3194            }
3195            source => HeldStorePositionReason::ObjectUnreadable {
3196                key,
3197                detail: source.to_string(),
3198            },
3199        },
3200    }
3201}
3202
3203async fn readiness(
3204    db: &Database,
3205    storage: &dyn SyncStorage,
3206    root: &StoreRootRef,
3207    coverage: &super::store_commit::CommitFrontier,
3208    frontier: &BTreeMap<String, StoreBatchCommitRef>,
3209    commit_ref: &StoreBatchCommitRef,
3210    commit: &StoreBatchCommit,
3211) -> Result<Readiness, StorePullError> {
3212    let stream_id = commit_stream_id(&commit_ref.coord);
3213    if let Some(current) = frontier.get(&stream_id) {
3214        if commit_ref.coord.sequence() <= current.coord.sequence() {
3215            match reference_is_materialized(db, storage, root, coverage, &stream_id, commit_ref)
3216                .await?
3217            {
3218                MaterializedCheck::Yes => return Ok(Readiness::AlreadyMaterialized),
3219                MaterializedCheck::Missing => {
3220                    return Ok(Readiness::Held(held_commit(
3221                        commit_ref,
3222                        HeldStorePositionReason::MissingCommit,
3223                    )))
3224                }
3225                MaterializedCheck::Held(reason) => {
3226                    return Ok(Readiness::Held(held_commit(commit_ref, reason)))
3227                }
3228            }
3229        }
3230        if commit.order.predecessor() != Some(current) {
3231            let reason = match commit.order.predecessor() {
3232                Some(missing) => HeldStorePositionReason::MissingPredecessor(missing.clone()),
3233                None => HeldStorePositionReason::InvalidObject(
3234                    "non-genesis Merge commit omits its exact predecessor".to_string(),
3235                ),
3236            };
3237            return Ok(Readiness::Held(held_commit(commit_ref, reason)));
3238        }
3239        if commit_ref.coord.sequence() != current.coord.sequence() + 1 {
3240            return Ok(Readiness::Held(held_commit(
3241                commit_ref,
3242                HeldStorePositionReason::InvalidObject(
3243                    "Merge commit sequence does not immediately follow its materialized frontier"
3244                        .to_string(),
3245                ),
3246            )));
3247        }
3248    } else if commit_ref.coord.sequence() != 1 || commit.order.predecessor().is_some() {
3249        let reason = match commit.order.predecessor() {
3250            Some(missing) => HeldStorePositionReason::MissingPredecessor(missing.clone()),
3251            None => HeldStorePositionReason::InvalidObject(
3252                "Merge commit beyond genesis omits its exact predecessor".to_string(),
3253            ),
3254        };
3255        return Ok(Readiness::Held(held_commit(commit_ref, reason)));
3256    }
3257
3258    for (required_stream, required_ref) in commit.merge_dependencies().map_err(|error| {
3259        StorePullError::Database(format!("MergeConcurrent commit order: {error}"))
3260    })? {
3261        let required_stream = required_stream.to_string();
3262        match reference_is_materialized(db, storage, root, coverage, &required_stream, required_ref)
3263            .await?
3264        {
3265            MaterializedCheck::Yes => {}
3266            MaterializedCheck::Missing => {
3267                return Ok(Readiness::Held(held_dependency(
3268                    commit_ref,
3269                    &required_stream,
3270                    required_ref,
3271                    HeldStorePositionReason::MissingDependency {
3272                        device_id: required_stream.clone(),
3273                        commit: required_ref.clone(),
3274                    },
3275                )))
3276            }
3277            MaterializedCheck::Held(reason) => {
3278                return Ok(Readiness::Held(held_dependency(
3279                    commit_ref,
3280                    &required_stream,
3281                    required_ref,
3282                    reason,
3283                )))
3284            }
3285        }
3286    }
3287    Ok(Readiness::Ready)
3288}
3289
3290async fn reference_is_materialized(
3291    db: &Database,
3292    storage: &dyn SyncStorage,
3293    root: &StoreRootRef,
3294    coverage: &super::store_commit::CommitFrontier,
3295    stream_id: &str,
3296    reference: &StoreBatchCommitRef,
3297) -> Result<MaterializedCheck, StorePullError> {
3298    if commit_stream_id(&reference.coord) != stream_id {
3299        return Ok(MaterializedCheck::Held(HeldStorePositionReason::WrongSlot(
3300            format!(
3301                "commit reference stream {} differs from dependency stream {stream_id}",
3302                commit_stream_id(&reference.coord)
3303            ),
3304        )));
3305    }
3306    if let Some(actual) = db
3307        .exact_materialized_ref(stream_id, reference.coord.sequence())
3308        .await?
3309    {
3310        if actual != *reference {
3311            return Ok(MaterializedCheck::Held(
3312                HeldStorePositionReason::HashMismatch {
3313                    referenced_device_id: stream_id.to_string(),
3314                    referenced_commit: reference.clone(),
3315                    materialized_hash: actual.commit_hash,
3316                },
3317            ));
3318        }
3319        return Ok(MaterializedCheck::Yes);
3320    }
3321    let coverage = coverage.clone().into_refs();
3322    let Some(covered) = coverage.get(stream_id) else {
3323        return Ok(MaterializedCheck::Missing);
3324    };
3325    if reference.coord.sequence() > covered.coord.sequence() {
3326        return Ok(MaterializedCheck::Missing);
3327    }
3328    let mut cursor = covered.clone();
3329    loop {
3330        if cursor == *reference {
3331            return Ok(MaterializedCheck::Yes);
3332        }
3333        if cursor.coord.sequence() <= reference.coord.sequence() {
3334            return Ok(MaterializedCheck::Held(
3335                HeldStorePositionReason::HashMismatch {
3336                    referenced_device_id: stream_id.to_string(),
3337                    referenced_commit: reference.clone(),
3338                    materialized_hash: cursor.commit_hash,
3339                },
3340            ));
3341        }
3342        let (commit, _) = match load_commit_with_author(storage, root, &cursor).await {
3343            Ok(commit) => commit,
3344            Err(error) => return Ok(MaterializedCheck::Held(held_object_error(error))),
3345        };
3346        let Some(predecessor) = commit.order.predecessor() else {
3347            return Ok(MaterializedCheck::Missing);
3348        };
3349        cursor = predecessor.clone();
3350    }
3351}
3352
3353async fn apply_candidate(
3354    db: &Database,
3355    storage: &dyn SyncStorage,
3356    store_dir: &StoreDir,
3357    schema: Arc<TableSchema>,
3358    candidate: &Candidate,
3359) -> Result<ApplyOutcome, StorePullError> {
3360    let Some(package_bytes) = candidate.package.as_ref() else {
3361        let commit = candidate.commit.clone();
3362        let commit_ref = candidate.commit_ref.clone();
3363        let registrations = candidate.registrations.clone();
3364        let circle_activations = candidate.circle_activations.clone();
3365        db.call(move |conn| {
3366            let tx = conn.unchecked_transaction().map_err(DbError::from)?;
3367            Database::record_activated_store_device_registrations_on(&tx, &commit, &registrations)?;
3368            Database::record_verified_circle_activations_on(
3369                &tx,
3370                &commit,
3371                &commit_ref,
3372                &circle_activations,
3373            )?;
3374            Database::record_materialized_commit_on(&tx, &commit, &commit_ref)?;
3375            tx.commit().map_err(DbError::from)
3376        })
3377        .await?;
3378        return Ok(ApplyOutcome::Applied(Vec::new()));
3379    };
3380    let package = match parse_candidate_store_package(candidate, package_bytes) {
3381        Ok(package) => package,
3382        Err(error) => {
3383            return Ok(ApplyOutcome::Held(
3384                HeldStorePositionReason::InvalidChangeset(error),
3385            ));
3386        }
3387    };
3388    let changeset = match ValidatedChangeset::new(package.changeset().to_vec(), schema) {
3389        Ok(changeset) => changeset,
3390        Err(error) => {
3391            return Ok(ApplyOutcome::Held(match error {
3392                super::session::ChangesetIdentityError::Row(error) => {
3393                    HeldStorePositionReason::InvalidRowIdentity {
3394                        table: error.table().to_string(),
3395                        reason: error.to_string(),
3396                    }
3397                }
3398                error => HeldStorePositionReason::InvalidChangeset(error.to_string()),
3399            }))
3400        }
3401    };
3402    let changes = match crate::changeset::walk(changeset.bytes()) {
3403        Ok(changes) => changes,
3404        Err(error) => {
3405            return Ok(ApplyOutcome::Held(
3406                HeldStorePositionReason::InvalidChangeset(error.to_string()),
3407            ))
3408        }
3409    };
3410    let old_changes = match crate::changeset::walk_old(changeset.bytes()) {
3411        Ok(changes) => changes,
3412        Err(error) => {
3413            return Ok(ApplyOutcome::Held(
3414                HeldStorePositionReason::InvalidChangeset(error.to_string()),
3415            ))
3416        }
3417    };
3418    let blob_decls = db.blob_decls();
3419    let eager = match cache_eager_blobs(&blob_decls, &changes, &package) {
3420        Ok(eager) => eager,
3421        Err(error) => {
3422            return Ok(ApplyOutcome::Held(
3423                HeldStorePositionReason::InvalidChangeset(error.to_string()),
3424            ))
3425        }
3426    };
3427    if let Err(failures) = download_blobs(db, eager, storage, store_dir).await {
3428        if failures.has_transport_failure() {
3429            return Err(StorePullError::BlobDownloads(failures));
3430        }
3431        return Ok(ApplyOutcome::Held(
3432            HeldStorePositionReason::BlobDownloadFailed,
3433        ));
3434    }
3435    let cleanup = match local_blob_cleanup_intents(&blob_decls, &old_changes, &changes) {
3436        Ok(cleanup) => cleanup,
3437        Err(error) => {
3438            return Ok(ApplyOutcome::Held(
3439                HeldStorePositionReason::InvalidChangeset(error.to_string()),
3440            ))
3441        }
3442    };
3443    let outcome = commit_candidate(db, candidate, package, changes, changeset, cleanup).await?;
3444    #[cfg(any(test, feature = "test-utils"))]
3445    if matches!(outcome, ApplyOutcome::Applied(_)) {
3446        db.reach_test_point(crate::database::DatabaseTestPoint::PullAfterRemoteCommit {
3447            device_id: commit_stream_id(&candidate.commit_ref.coord),
3448            seq: candidate.commit.seq(),
3449        })
3450        .await;
3451    }
3452    Ok(outcome)
3453}
3454
3455async fn commit_candidate(
3456    db: &Database,
3457    candidate: &Candidate,
3458    package: AudiencePackage,
3459    changes: Vec<RowChange>,
3460    changeset: ValidatedChangeset<Vec<u8>>,
3461    cleanup: Vec<LocalBlobCleanupIntent>,
3462) -> Result<ApplyOutcome, StorePullError> {
3463    let commit = candidate.commit.clone();
3464    let commit_ref = candidate.commit_ref.clone();
3465    let registrations = candidate.registrations.clone();
3466    let circle_activations = candidate.circle_activations.clone();
3467    let returned_changes = changes.clone();
3468    let receiver_wall_ms = db.receive_wall_ms();
3469    let blob_decls = db.blob_decls();
3470    let gates = db.gates();
3471    let synced_tables = db.synced_tables().to_vec();
3472    let mut changeset_max = None;
3473    advance_max_updated_at(
3474        &mut changeset_max,
3475        &changes,
3476        changeset.schema(),
3477        receiver_wall_ms,
3478    );
3479    let hlc = db.hlc();
3480    let outcome = db
3481        .call(move |conn| {
3482            let tx = conn.unchecked_transaction().map_err(DbError::from)?;
3483            let apply =
3484                resolve_and_apply_changeset_with_schema_on(&tx, changeset, receiver_wall_ms)?;
3485            if !apply.constraint_conflict_tables.is_empty() {
3486                tx.rollback().map_err(DbError::from)?;
3487                return Ok(ApplyOutcome::Held(
3488                    HeldStorePositionReason::ConstraintConflict(apply.constraint_conflict_tables),
3489                ));
3490            }
3491            if apply.had_fk_violations {
3492                tx.rollback().map_err(DbError::from)?;
3493                return Ok(ApplyOutcome::Held(
3494                    HeldStorePositionReason::ForeignKeyDependency,
3495                ));
3496            }
3497            let winning_rows =
3498                crate::sync::apply::current_winning_rows(&tx, &synced_tables, package.changeset())?;
3499            Database::install_pulled_blob_activations_on(&tx, &package, &commit_ref)?;
3500            Database::install_winning_blob_bindings_on(
3501                &tx,
3502                &gates,
3503                &synced_tables,
3504                &package,
3505                &BlobActivation {
3506                    coord: commit_ref.coord.clone(),
3507                },
3508                &winning_rows,
3509            )?;
3510            for intent in cleanup {
3511                local_cleanup::record_if_unreferenced_on(&tx, &blob_decls, &intent)?;
3512            }
3513            Database::record_activated_store_device_registrations_on(&tx, &commit, &registrations)?;
3514            Database::record_verified_circle_activations_on(
3515                &tx,
3516                &commit,
3517                &commit_ref,
3518                &circle_activations,
3519            )?;
3520            Database::record_materialized_commit_on(&tx, &commit, &commit_ref)?;
3521            tx.commit().map_err(DbError::from)?;
3522            if let Some(max_applied) = changeset_max.as_ref() {
3523                hlc.advance_past(max_applied);
3524            }
3525            Ok(ApplyOutcome::Applied(returned_changes))
3526        })
3527        .await?;
3528    Ok(outcome)
3529}
3530
3531fn held_commit(
3532    reference: &StoreBatchCommitRef,
3533    reason: HeldStorePositionReason,
3534) -> HeldStorePosition {
3535    HeldStorePosition {
3536        coordinate: HeldStoreCoordinate::Commit {
3537            device_id: commit_stream_id(&reference.coord),
3538            commit: reference.clone(),
3539        },
3540        reason,
3541    }
3542}
3543
3544fn held_package(
3545    reference: &StoreBatchCommitRef,
3546    commit: &StoreBatchCommit,
3547    reason: HeldStorePositionReason,
3548) -> HeldStorePosition {
3549    let package = commit
3550        .store_package()
3551        .expect("held Store package is named by the commit");
3552    HeldStorePosition {
3553        coordinate: HeldStoreCoordinate::Package {
3554            device_id: commit_stream_id(&reference.coord),
3555            seq: commit.seq(),
3556            package_hash: package.content_hash,
3557        },
3558        reason,
3559    }
3560}
3561
3562fn held_dependency(
3563    dependent: &StoreBatchCommitRef,
3564    required_device_id: &str,
3565    required: &StoreBatchCommitRef,
3566    reason: HeldStorePositionReason,
3567) -> HeldStorePosition {
3568    HeldStorePosition {
3569        coordinate: HeldStoreCoordinate::Dependency {
3570            dependent_device_id: commit_stream_id(&dependent.coord),
3571            dependent_commit: dependent.clone(),
3572            required_device_id: required_device_id.to_string(),
3573            required_commit: required.clone(),
3574        },
3575        reason,
3576    }
3577}
3578
3579fn commit_stream_id(coord: &StoreCommitCoord) -> String {
3580    match coord {
3581        StoreCommitCoord::MergeConcurrent { stream_id, .. } => stream_id.to_string(),
3582        StoreCommitCoord::Serial { .. } => SERIAL_STREAM_ID.to_string(),
3583    }
3584}
3585
3586#[cfg(test)]
3587mod tests {
3588    use super::*;
3589
3590    #[tokio::test]
3591    async fn cycle_authorization_rejects_an_absent_serial_coordination_head() {
3592        let db = crate::sync::test_helpers::open_serial_test_db();
3593        let store = crate::sync::test_helpers::TestStore::create(
3594            &db,
3595            "absent-serial-cycle-head",
3596            crate::keys::UserKeypair::generate(),
3597        )
3598        .await
3599        .expect("create Serial Store");
3600        store.home.remove(serial_head_key());
3601
3602        let result = load_serial_cycle_authorization(
3603            &store.storage,
3604            store
3605                .storage
3606                .serial_coordination()
3607                .expect("Serial coordination"),
3608            &store.root,
3609        )
3610        .await;
3611
3612        assert!(matches!(
3613            result,
3614            Err(StorePullError::Serial(reason)) if reason == "global head is absent"
3615        ));
3616    }
3617
3618    #[tokio::test]
3619    async fn merge_gap_reports_the_exact_signed_predecessor() {
3620        let source = crate::sync::test_helpers::open_test_db();
3621        let store = crate::sync::test_helpers::TestStore::create(
3622            &source,
3623            "exact-predecessor-test",
3624            crate::keys::UserKeypair::generate(),
3625        )
3626        .await
3627        .expect("create exact predecessor test Store");
3628        let changeset = crate::sync::test_helpers::capture_bytes(
3629            &crate::sync::test_helpers::open_test_db(),
3630            &[
3631                "INSERT INTO notes (id, title, body, _updated_at, created_at) \
3632               VALUES ('gap-row', 'gap', NULL, '0000000001000-0000-gap', '2026-01-01')",
3633            ],
3634        )
3635        .await;
3636        let first = store
3637            .publish_changeset("founder", 1, &changeset, source.schema_version())
3638            .await
3639            .expect("publish first exact commit");
3640        let second = store
3641            .publish_changeset("founder", 2, &changeset, source.schema_version())
3642            .await
3643            .expect("publish second exact commit");
3644        let third = store
3645            .publish_changeset("founder", 3, &changeset, source.schema_version())
3646            .await
3647            .expect("publish third exact commit");
3648        let (_, founder, _) = store
3649            .founder_device_authority()
3650            .await
3651            .expect("load founder authority");
3652        let commit = super::super::store_objects::load_commit_ref(
3653            &store.storage,
3654            store.root.store_root_hash,
3655            &third,
3656            &founder,
3657        )
3658        .await
3659        .expect("load third exact commit")
3660        .value;
3661        let stream_id = commit_stream_id(&first.coord);
3662        let frontier = BTreeMap::from([(stream_id.clone(), first.clone())]);
3663        let coverage =
3664            CommitFrontier::from_refs(crate::WritePolicy::MergeConcurrent, frontier.clone())
3665                .expect("build exact frontier");
3666        let target = crate::sync::test_helpers::open_test_db();
3667
3668        let readiness = readiness(
3669            &target,
3670            &store.storage,
3671            &store.root,
3672            &coverage,
3673            &frontier,
3674            &third,
3675            &commit,
3676        )
3677        .await
3678        .expect("evaluate exact predecessor gap");
3679
3680        assert!(matches!(
3681            readiness,
3682            Readiness::Held(HeldStorePosition {
3683                reason: HeldStorePositionReason::MissingPredecessor(missing),
3684                ..
3685            }) if missing == second
3686        ));
3687    }
3688}