Skip to main content

coven_database/store/store_session/
store_authority.rs

1use crate::*;
2use crate::{RetainedReplayAuthority, RetainedReplayGenesisAuthority, GENERATION_ZERO};
3use coven_protocol::store_commit::{
4    ResolvedStoreDeviceState, StoreAckRef, StoreDeviceRegistrationRef,
5};
6use rusqlite::OptionalExtension;
7
8use super::*;
9
10impl StoreSession<'_> {
11    fn membership_head_cursors(&mut self) -> Result<InitialStoreMembershipAuthority, DbError> {
12        InitialStoreMembershipAuthority::load_on(self.conn)
13    }
14
15    fn persist_membership_head_cursors(
16        &mut self,
17        head_refs: Vec<coven_protocol::membership::MembershipHeadRef>,
18    ) -> Result<(), DbError> {
19        let transaction = self.conn.unchecked_transaction().map_err(DbError::from)?;
20        InitialStoreMembershipAuthority { head_refs }.install_on(&transaction)?;
21        transaction.commit().map_err(DbError::from)
22    }
23
24    fn validated_store_owner(
25        &mut self,
26        expected_root: coven_protocol::store_commit::StoreRootRef,
27    ) -> Result<String, DbError> {
28        let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
29        let (root, protocol_root) = self
30            .verified_store_authority
31            .root_authority_on(records)?
32            .ok_or(DbError::StoreRootHashMissing)?;
33        if root != expected_root {
34            return Err(DbError::Message(
35                "local Store root differs from the operation authority".to_string(),
36            ));
37        }
38        let owner = get_protocol_state_on(
39            self.conn,
40            coven_protocol::membership::OWNER_PUBKEY_STATE_KEY,
41        )?
42        .ok_or_else(|| DbError::Message("Store owner anchor is absent".to_string()))?;
43        if owner != protocol_root.descriptor.founder_pubkey {
44            return Err(DbError::Message(
45                "Store owner anchor differs from its signed root".to_string(),
46            ));
47        }
48        let baseline = self
49            .verified_store_authority
50            .retained_replay_baseline_on(records)?;
51        let owner_authority = match &baseline.authority {
52            RetainedReplayAuthority::Genesis(authority) => authority.clone(),
53            RetainedReplayAuthority::InstalledSnapshot(authority) => {
54                RetainedReplayGenesisAuthority {
55                    store_root: authority.store_root.clone(),
56                    founder_registration: authority.founder_registration.clone(),
57                }
58            }
59        };
60        if owner_authority.store_root != root {
61            return Err(DbError::Message(
62                "retained replay baseline belongs to another Store root".to_string(),
63            ));
64        }
65        let founder = self.verified_store_authority.activated_registration_on(
66            records,
67            &root,
68            &owner_authority.founder_registration,
69        )?;
70        let expected_genesis = ResolvedStoreDeviceState::founder(
71            &root,
72            owner_authority.founder_registration.clone(),
73            &protocol_root.descriptor.founder_pubkey,
74            protocol_root.descriptor.founder_grant.clone(),
75            &protocol_root.descriptor.founder_recovery,
76        )
77        .map_err(DbError::from)?;
78        let stored_genesis: ResolvedStoreDeviceState = serde_json::from_str(
79            &required_protocol_state_on(self.conn, STORE_DEVICE_GENESIS_STATE_KEY)?,
80        )
81        .map_err(|error| DbError::context("Store device genesis state", error))?;
82        if founder.author_pubkey != owner || stored_genesis != expected_genesis {
83            return Err(DbError::Message(
84                "Store device genesis differs from installed founder authority".to_string(),
85            ));
86        }
87        self.verified_store_authority
88            .remember_verified_owner_anchor(owner_authority)?;
89        Ok(owner)
90    }
91
92    fn install_store_owner_anchor(
93        &mut self,
94        anchor: crate::StoreOwnerAnchor,
95        membership: InitialStoreMembershipAuthority,
96    ) -> Result<(), DbError> {
97        if self.verified_store_authority.reuses_owner_anchor(&anchor)? {
98            return self.persist_membership_head_cursors(membership.head_refs);
99        }
100        let authority = anchor.authority().clone();
101        let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
102        let root_value =
103            install_store_root_authority_on(&tx, &authority.store_root, &anchor.root().bytes)?;
104        install_store_founder_state_on(
105            &tx,
106            &authority.store_root,
107            &authority.founder_registration,
108            &anchor.founder().value,
109            &anchor.founder().bytes,
110            anchor.genesis(),
111        )?;
112        set_protocol_state_on(
113            &tx,
114            coven_protocol::membership::OWNER_PUBKEY_STATE_KEY,
115            anchor.owner(),
116        )?;
117        membership.install_on(&tx)?;
118        let baseline = crate::store::store_session::StoreTransaction::new(&tx, self.store_dir)
119            .ensure_founder_replay_baseline(
120                self.schema_version,
121                self.sync_routing_hash,
122                authority.clone(),
123            )?;
124        tx.commit().map_err(DbError::from)?;
125        self.verified_store_authority.commit_installed_owner_anchor(
126            authority,
127            root_value,
128            anchor.founder().value.clone(),
129            baseline,
130        );
131        Ok(())
132    }
133
134    fn local_store_founder_graph(&mut self) -> Result<Option<Box<DurableFounderGraph>>, DbError> {
135        load_local_store_founder_graph_on(self.conn)
136    }
137
138    fn stage_store_founder_graph(
139        &mut self,
140        graph: Box<DurableFounderGraph>,
141    ) -> Result<(), DbError> {
142        let conn = self.conn;
143        let tx = conn.unchecked_transaction().map_err(DbError::from)?;
144        if let Some(existing) = load_local_store_founder_graph_on(&tx)? {
145            existing.validate()?;
146            if founder_graph_identity(&existing) == founder_graph_identity(&graph) {
147                return Ok(());
148            }
149            return Err(DbError::Message(
150                "local Store founder graph already owns different exact objects".to_string(),
151            ));
152        }
153        use coven_protocol::provider::{ExactProbeProgress, ProviderProbeJournalRecord};
154        use coven_protocol::store_creation::{
155            StoreCreationAttempt, STORE_CREATION_ATTEMPT_STATE_KEY,
156        };
157
158        let attempt_json =
159            crate::required_protocol_state_on(&tx, STORE_CREATION_ATTEMPT_STATE_KEY)?;
160        let attempt: StoreCreationAttempt = serde_json::from_str(&attempt_json)
161            .map_err(|error| DbError::context("parse Store creation attempt", error))?;
162        let StoreCreationAttempt::FounderGraphReserved(graph_reservation) = attempt else {
163            return Err(DbError::Message(
164                "Store creation attempt has not reserved the complete founder graph".to_string(),
165            ));
166        };
167        let reservation = &graph_reservation.descriptor;
168        let descriptor = &graph.root.value.descriptor;
169        let founder = &reservation.membership.founder;
170        let authority = &founder.root.authority;
171        if authority.creation_id != descriptor.creation_id
172            || authority.founder_grant != descriptor.founder_grant
173            || authority.provider_admin_grant != descriptor.founder_provider_admin.grant_id
174            || authority.binding.store != descriptor.provider
175            || authority.binding.device != descriptor.founder_provider_admin.provider
176            || authority.founder_pubkey != descriptor.founder_pubkey
177            || authority.schema_version != descriptor.schema_version
178            || authority.sync_routing_hash != descriptor.sync_routing_hash
179            || founder.root.root_slot != descriptor.root_slot
180            || reservation.current_publication_slot != descriptor.current_publication_slot
181            || founder.registration_slot != descriptor.founder_registration
182            || &reservation.recovery_slot != descriptor.founder_recovery.first_slot()
183            || descriptor.founder_membership.first_slot() != &reservation.membership.first_slot
184        {
185            return Err(DbError::Message(
186                "signed Store descriptor differs from its durable creation attempt".to_string(),
187            ));
188        }
189        if graph.registration.value.store_commits != graph_reservation.store_commits
190            || graph.registration.value.acknowledgements != graph_reservation.acknowledgements
191            || graph.registration.value.snapshots != graph_reservation.snapshots
192            || graph.initial_ack.value.last_sync != authority.founder_timestamp
193            || graph.initial_ack.value.successor.next_slot != graph_reservation.next_ack_slot
194            || graph.membership.entry.value.created_at != authority.founder_timestamp
195            || graph.membership.head.value.body.successor.next_slot
196                != graph_reservation.membership.next_head_slot
197        {
198            return Err(DbError::Message(
199                "signed founder graph differs from its durable slot reservation".to_string(),
200            ));
201        }
202
203        let exact_key = format!(
204            "provider_probe/{}",
205            hex::encode(authority.probes.exact_slots().as_bytes())
206        );
207        let exact_json = crate::required_protocol_state_on(&tx, &exact_key)?;
208        let exact: ProviderProbeJournalRecord = serde_json::from_str(&exact_json)
209            .map_err(|error| DbError::context("parse provider probe journal", error))?;
210        let ProviderProbeJournalRecord::Exact(exact) = exact else {
211            return Err(DbError::Message(
212                "Store creation exact probe id names another probe kind".to_string(),
213            ));
214        };
215        let ExactProbeProgress::ReceiptReady { receipt } = exact.progress else {
216            return Err(DbError::Message(
217                "Store creation exact probe has no terminal receipt".to_string(),
218            ));
219        };
220        if receipt != descriptor.founder_provider_admin.capability.exact_slots {
221            return Err(DbError::Message(
222                "signed Store descriptor differs from its terminal exact probe".to_string(),
223            ));
224        }
225        for key in [exact_key, STORE_CREATION_ATTEMPT_STATE_KEY.to_string()] {
226            let deleted = crate::delete_protocol_state_on(&tx, &key)?;
227            if deleted != 1 {
228                return Err(DbError::Message(
229                    "Store creation journal disappeared during typed consumption".to_string(),
230                ));
231            }
232        }
233        tx.execute(
234            "INSERT INTO local_store_protocol_root \
235         (singleton, store_root_hash, store_protocol_root_bytes, prepared_object) \
236         VALUES (1, ?1, ?2, ?3)",
237            rusqlite::params![
238                graph.root.value.object_hash().to_string(),
239                graph.root.bytes,
240                serde_json::to_string(&graph.root.prepared).map_err(|error| {
241                    DbError::context("serialize prepared Store root", error)
242                })?,
243            ],
244        )
245        .map_err(DbError::from)?;
246        tx.execute(
247            "INSERT INTO local_store_device_registration \
248         (singleton, device_id, registration_hash, registration_bytes, prepared_object, \
249          initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state) \
250         VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
251            rusqlite::params![
252                graph.registration.value.device_id.to_string(),
253                graph.registration.value.registration_hash().to_string(),
254                graph.registration.bytes,
255                serde_json::to_string(&graph.registration.prepared).map_err(|error| {
256                    DbError::context("serialize prepared founder registration", error)
257                })?,
258                serde_json::to_string(&graph.initial_ack_ref).map_err(|error| {
259                    DbError::context("serialize founder initial ack ref", error)
260                })?,
261                graph.initial_ack.bytes,
262                serde_json::to_string(&graph.initial_ack.prepared).map_err(|error| {
263                    DbError::context("serialize founder initial ack object", error)
264                })?,
265                serde_json::to_string(&LocalDeviceRegistrationState::Prepared).map_err(
266                    |error| DbError::context("serialize registration journal state", error)
267                )?,
268            ],
269        )
270        .map_err(DbError::from)?;
271        tx.execute(
272            "INSERT INTO local_store_founder_graph \
273         (singleton, membership_graph) VALUES (1, ?1)",
274            rusqlite::params![
275                serde_json::to_string(&DurableFounderMembershipJournal::from_graph(
276                    &graph.membership,
277                ))
278                .map_err(|error| DbError::context("serialize founder membership graph", error))?,
279            ],
280        )
281        .map_err(DbError::from)?;
282        tx.commit().map_err(DbError::from)
283    }
284
285    fn complete_store_founder_graph(
286        &mut self,
287        expected_root: coven_protocol::store_commit::StoreRootRef,
288        expected_registration: StoreDeviceRegistrationRef,
289        expected_initial_ack: StoreAckRef,
290        expected_membership: FounderMembershipRefs,
291        current_publication: crate::ObservedStorePublication,
292    ) -> Result<(), DbError> {
293        let schema_version = self.schema_version;
294        let routing_hash = self.sync_routing_hash;
295        let verified_authority = &mut *self.verified_store_authority;
296        let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
297        let store_dir = self.store_dir;
298        let graph = load_local_store_founder_graph_on(&tx)?
299            .ok_or_else(|| DbError::Message("local Store founder graph is absent".to_string()))?;
300        let root = coven_protocol::store_commit::StoreRootRef {
301            store_root_id: graph.root.value.descriptor.store_root_id(),
302            store_root_hash: graph.root.value.object_hash(),
303            object: graph.root.prepared.reference().clone(),
304        };
305        let registration = StoreDeviceRegistrationRef::from_registration(
306            &graph.registration.value,
307            graph.registration.prepared.reference().clone(),
308        );
309        if root != expected_root
310            || registration != expected_registration
311            || graph.initial_ack_ref != expected_initial_ack
312            || graph.membership.entry_ref != expected_membership.entry
313            || graph.membership.head_ref != expected_membership.head
314        {
315            return Err(DbError::Message(
316                "verified founder graph differs from its durable exact references".to_string(),
317            ));
318        }
319        let founder_authority =
320            coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
321                root: root.clone(),
322            };
323        let device_genesis = ResolvedStoreDeviceState::founder(
324            &root,
325            registration.clone(),
326            &graph.root.value.descriptor.founder_pubkey,
327            graph.root.value.descriptor.founder_grant.clone(),
328            &graph.root.value.descriptor.founder_recovery,
329        )
330        .map_err(DbError::from)?;
331        let device_genesis_json = serde_json::to_string(&device_genesis)
332            .map_err(|error| DbError::context("serialize Store device genesis state", error))?;
333        let device_id = registration.device_id.to_string();
334        let registration_hash = registration.registration_hash.to_string();
335        match &graph.registration_state {
336            LocalDeviceRegistrationState::Prepared
337            | LocalDeviceRegistrationState::RegistrationPublished
338            | LocalDeviceRegistrationState::RegistrationActivated { .. } => {
339                return Err(DbError::Message(
340                    "founder registration and initial acknowledgement are not exact-created"
341                        .to_string(),
342                ));
343            }
344            LocalDeviceRegistrationState::Created => {}
345            LocalDeviceRegistrationState::Activated { authority } => {
346                if authority != &founder_authority {
347                    return Err(DbError::Message(
348                        "founder registration journal carries another activation authority"
349                            .to_string(),
350                    ));
351                }
352                let store_transaction =
353                    crate::store::store_session::StoreTransaction::new(&tx, store_dir);
354                let installed = store_transaction.root_authority(verified_authority)?;
355                let installed_registration = store_transaction.activated_registration(
356                    verified_authority,
357                    &root,
358                    &registration,
359                )?;
360                let stored: Option<(String, String, String)> = tx
361                    .query_row(
362                        "SELECT registration_object, activation_authority, registration_hash \
363                         FROM store_device_registration_activations WHERE device_id = ?1",
364                        [&device_id],
365                        |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
366                    )
367                    .optional()
368                    .map_err(DbError::from)?;
369                let ack: Option<String> = tx
370                    .query_row(
371                        "SELECT ack_ref FROM published_store_acks WHERE singleton = 1",
372                        [],
373                        |row| row.get(0),
374                    )
375                    .optional()
376                    .map_err(DbError::from)?;
377                let stored_device_genesis =
378                    crate::get_protocol_state_on(&tx, STORE_DEVICE_GENESIS_STATE_KEY)?;
379                if installed
380                    .as_ref()
381                    .map(|(reference, value)| (reference, value))
382                    != Some((&root, &graph.root.value))
383                    || installed_registration != graph.registration.value
384                    || stored
385                        != Some((
386                            serde_json::to_string(&registration).map_err(|error| {
387                                DbError::context("serialize founder registration ref", error)
388                            })?,
389                            serde_json::to_string(&founder_authority).map_err(|error| {
390                                DbError::context("serialize founder authority", error)
391                            })?,
392                            registration.registration_hash.to_string(),
393                        ))
394                    || ack
395                        != Some(
396                            serde_json::to_string(&graph.initial_ack_ref).map_err(|error| {
397                                DbError::context("serialize founder ack ref", error)
398                            })?,
399                        )
400                    || stored_device_genesis.as_deref() != Some(&device_genesis_json)
401                {
402                    return Err(DbError::Message(
403                        "activated founder journal differs from installed exact authority"
404                            .to_string(),
405                    ));
406                }
407                let publication =
408                    super::observed_store_publication::load_store_current_publication_on(&tx)?;
409                if publication != current_publication {
410                    return Err(DbError::Message(
411                        "activated founder journal differs from its Store publication record"
412                            .to_string(),
413                    ));
414                }
415                let owner_authority = RetainedReplayGenesisAuthority {
416                    store_root: root.clone(),
417                    founder_registration: registration.clone(),
418                };
419                let baseline_matches = {
420                    let baseline =
421                        crate::store::store_session::StoreTransaction::new(&tx, store_dir)
422                            .retained_replay_baseline(verified_authority)?;
423                    baseline.generation == GENERATION_ZERO
424                        && baseline.schema_version == schema_version
425                        && baseline.routing_hash == routing_hash
426                        && baseline.authority
427                            == RetainedReplayAuthority::Genesis(owner_authority.clone())
428                };
429                if !baseline_matches {
430                    return Err(DbError::Message(
431                        "activated founder state differs from its generation-zero replay baseline"
432                            .to_string(),
433                    ));
434                }
435                verified_authority.remember_verified_owner_anchor(owner_authority)?;
436                return Ok(());
437            }
438        }
439        let root_value = install_store_root_authority_on(&tx, &root, &graph.root.bytes)?;
440        current_publication
441            .record()
442            .verify_genesis(
443                root.store_root_hash,
444                &graph.root.value.descriptor.founder_pubkey,
445            )
446            .map_err(DbError::from)?;
447        super::observed_store_publication::install_genesis_store_publication_on(
448            &tx,
449            current_publication.record(),
450            current_publication.version(),
451        )?;
452        let activation = serde_json::to_string(
453            &coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
454                root: root.clone(),
455            },
456        )
457        .map_err(|error| DbError::context("serialize founder registration activation", error))?;
458        let journal_state = serde_json::to_string(&LocalDeviceRegistrationState::Activated {
459            authority: founder_authority,
460        })
461        .map_err(|error| DbError::context("serialize founder registration journal", error))?;
462        let updated = tx
463            .execute(
464                "UPDATE local_store_device_registration SET state = ?1 \
465                 WHERE singleton = 1 AND device_id = ?2 AND registration_hash = ?3 \
466                   AND initial_ack_ref = ?4 AND state = ?5",
467                rusqlite::params![
468                    journal_state,
469                    &device_id,
470                    &registration_hash,
471                    serde_json::to_string(&graph.initial_ack_ref).map_err(|error| {
472                        DbError::context("serialize founder initial ack ref", error)
473                    })?,
474                    serde_json::to_string(&LocalDeviceRegistrationState::Created).map_err(
475                        |error| DbError::context("serialize created journal state", error)
476                    )?,
477                ],
478            )
479            .map_err(DbError::from)?;
480        if updated != 1 {
481            return Err(DbError::Message(
482                "founder registration journal did not activate".to_string(),
483            ));
484        }
485        tx.execute(
486            "INSERT INTO store_device_registration_activations \
487             (device_id, registration_hash, author_pubkey, device_signing_pubkey, \
488              registration_bytes, registration_object, activation_authority) \
489             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
490            rusqlite::params![
491                &device_id,
492                &registration_hash,
493                graph.registration.value.author_pubkey,
494                graph.registration.value.device_signing_pubkey,
495                graph.registration.bytes,
496                serde_json::to_string(&registration).map_err(|error| {
497                    DbError::context("serialize founder registration ref", error)
498                })?,
499                activation,
500            ],
501        )
502        .map_err(DbError::from)?;
503        tx.execute(
504            "INSERT INTO published_store_acks \
505             (singleton, ack_ref, successor_slot) VALUES (1, ?1, ?2)",
506            rusqlite::params![
507                serde_json::to_string(&graph.initial_ack_ref).map_err(|error| {
508                    DbError::context("serialize founder initial ack ref", error)
509                })?,
510                serde_json::to_string(&graph.initial_ack.value.successor.next_slot)
511                    .map_err(|error| DbError::context("serialize founder ack successor", error))?,
512            ],
513        )
514        .map_err(DbError::from)?;
515        for (key, value) in [
516            (LOCAL_DEVICE_ID_STATE_KEY, device_id),
517            (STORE_DEVICE_GENESIS_STATE_KEY, device_genesis_json),
518        ] {
519            crate::set_protocol_state_on(&tx, key, &value)?;
520        }
521        crate::set_protocol_state_on(
522            &tx,
523            coven_protocol::membership::OWNER_PUBKEY_STATE_KEY,
524            &graph.root.value.descriptor.founder_pubkey,
525        )?;
526        crate::InitialStoreMembershipAuthority {
527            head_refs: vec![graph.membership.head_ref.clone()],
528        }
529        .install_on(&tx)?;
530        let baseline = crate::store::store_session::StoreTransaction::new(&tx, store_dir)
531            .install_generation_zero_replay_baseline(
532                schema_version,
533                routing_hash,
534                RetainedReplayGenesisAuthority {
535                    store_root: root.clone(),
536                    founder_registration: registration.clone(),
537                },
538            )?;
539        tx.commit().map_err(DbError::from)?;
540        verified_authority.commit_installed_owner_anchor(
541            RetainedReplayGenesisAuthority {
542                store_root: root,
543                founder_registration: registration,
544            },
545            root_value,
546            graph.registration.value,
547            baseline,
548        );
549        Ok(())
550    }
551}
552
553impl StoreDatabase {
554    pub async fn membership_head_cursors(
555        &self,
556    ) -> Result<crate::InitialStoreMembershipAuthority, DbError> {
557        self.call_store(|session| session.membership_head_cursors())
558            .await
559    }
560
561    pub async fn persist_membership_head_cursors(
562        &self,
563        head_refs: Vec<coven_protocol::membership::MembershipHeadRef>,
564    ) -> Result<(), DbError> {
565        self.call_store(move |session| session.persist_membership_head_cursors(head_refs))
566            .await
567    }
568
569    pub async fn local_store_root_ref(
570        &self,
571    ) -> Result<Option<coven_protocol::store_commit::StoreRootRef>, DbError> {
572        self.call_store(|session| {
573            session
574                .root_authority()
575                .map(|authority| authority.map(|(reference, _)| reference))
576        })
577        .await
578    }
579
580    pub async fn validated_store_owner(
581        &self,
582        expected_root: &coven_protocol::store_commit::StoreRootRef,
583    ) -> Result<String, DbError> {
584        let expected_root = expected_root.clone();
585        self.call_store(move |session| session.validated_store_owner(expected_root))
586            .await
587    }
588
589    pub async fn install_store_owner_anchor(
590        &self,
591        anchor: crate::StoreOwnerAnchor,
592        membership: InitialStoreMembershipAuthority,
593    ) -> Result<(), DbError> {
594        self.call_store(move |session| session.install_store_owner_anchor(anchor, membership))
595            .await
596    }
597
598    pub async fn local_store_founder_graph(
599        &self,
600    ) -> Result<Option<Box<DurableFounderGraph>>, DbError> {
601        self.call_store(|session| session.local_store_founder_graph())
602            .await
603    }
604
605    pub async fn stage_store_founder_graph(
606        &self,
607        graph: Box<DurableFounderGraph>,
608    ) -> Result<(), DbError> {
609        graph.validate()?;
610        self.call_store(move |session| session.stage_store_founder_graph(graph))
611            .await
612    }
613
614    pub async fn complete_store_founder_graph(
615        &self,
616        expected_root: coven_protocol::store_commit::StoreRootRef,
617        expected_registration: StoreDeviceRegistrationRef,
618        expected_initial_ack: StoreAckRef,
619        expected_membership: FounderMembershipRefs,
620        current_publication: crate::ObservedStorePublication,
621    ) -> Result<(), DbError> {
622        self.call_store(move |session| {
623            session.complete_store_founder_graph(
624                expected_root,
625                expected_registration,
626                expected_initial_ack,
627                expected_membership,
628                current_publication,
629            )
630        })
631        .await
632    }
633}