Skip to main content

coven_database/
store_authority_records.rs

1use super::*;
2
3#[derive(Debug, Clone)]
4pub struct StoreOwnerAnchor {
5    root: coven_protocol::objects::VerifiedObject<StoreProtocolRoot>,
6    founder: coven_protocol::objects::VerifiedObject<StoreDeviceRegistration>,
7    authority: RetainedReplayGenesisAuthority,
8    genesis: ResolvedStoreDeviceState,
9}
10
11impl StoreOwnerAnchor {
12    pub fn new(
13        root_reference: coven_protocol::store_commit::StoreRootRef,
14        root: coven_protocol::objects::VerifiedObject<StoreProtocolRoot>,
15        founder_reference: StoreDeviceRegistrationRef,
16        founder: coven_protocol::objects::VerifiedObject<StoreDeviceRegistration>,
17    ) -> Result<Self, DbError> {
18        let parsed_root_reference = coven_protocol::store_commit::StoreRootRef {
19            store_root_id: root.value.descriptor.store_root_id(),
20            store_root_hash: root.value.object_hash(),
21            object: root.object.clone(),
22        };
23        if root.bytes != root.value.to_bytes()
24            || root.semantic_hash != root.value.object_hash()
25            || parsed_root_reference != root_reference
26        {
27            return Err(DbError::Message(
28                "Store owner root differs from its verified exact object".to_string(),
29            ));
30        }
31        let parsed_founder_reference =
32            StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
33        if founder.bytes != founder.value.to_bytes()
34            || founder.semantic_hash != founder.value.registration_hash()
35            || parsed_founder_reference != founder_reference
36            || founder.value.author_pubkey != root.value.descriptor.founder_pubkey
37            || founder.object.slot() != &root.value.descriptor.founder_registration
38            || founder.value.provider != root.value.descriptor.founder_provider_admin.provider
39            || !matches!(
40                founder.value.origin,
41                coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Founder { .. }
42            )
43        {
44            return Err(DbError::Message(
45                "Store owner founder registration differs from its root or verified exact object"
46                    .to_string(),
47            ));
48        }
49        let genesis = ResolvedStoreDeviceState::founder(
50            &root_reference,
51            founder_reference.clone(),
52            &root.value.descriptor.founder_pubkey,
53            root.value.descriptor.founder_grant.clone(),
54            &root.value.descriptor.founder_recovery,
55        )
56        .map_err(DbError::from)?;
57        Ok(Self {
58            root,
59            founder,
60            authority: RetainedReplayGenesisAuthority {
61                store_root: root_reference,
62                founder_registration: founder_reference,
63            },
64            genesis,
65        })
66    }
67
68    pub(crate) fn authority(&self) -> &RetainedReplayGenesisAuthority {
69        &self.authority
70    }
71
72    pub(crate) fn root(&self) -> &coven_protocol::objects::VerifiedObject<StoreProtocolRoot> {
73        &self.root
74    }
75
76    pub(crate) fn founder(
77        &self,
78    ) -> &coven_protocol::objects::VerifiedObject<StoreDeviceRegistration> {
79        &self.founder
80    }
81
82    pub(crate) fn genesis(&self) -> &ResolvedStoreDeviceState {
83        &self.genesis
84    }
85
86    pub(crate) fn owner(&self) -> &str {
87        &self.root.value.descriptor.founder_pubkey
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct DurableFounderGraph {
93    pub root: ExactProtocolObject<StoreProtocolRoot>,
94    pub registration: ExactProtocolObject<StoreDeviceRegistration>,
95    pub initial_ack: ExactProtocolObject<StoreAck>,
96    pub initial_ack_ref: StoreAckRef,
97    pub membership: DurableFounderMembership,
98    pub registration_state: LocalDeviceRegistrationState,
99}
100
101impl DurableFounderGraph {
102    pub fn validate(&self) -> Result<(), DbError> {
103        let root = StoreProtocolRoot::parse(&self.root.bytes)
104            .map_err(|error| DbError::context("founder Store root", error))?;
105        if root != self.root.value || root.object_hash() != self.root.value.object_hash() {
106            return Err(DbError::Message(
107                "founder Store root differs from its prepared exact object".to_string(),
108            ));
109        }
110        let root_ref = coven_protocol::store_commit::StoreRootRef {
111            store_root_id: root.descriptor.store_root_id(),
112            store_root_hash: root.object_hash(),
113            object: self.root.prepared.reference().clone(),
114        };
115        let registration = StoreDeviceRegistration::parse_at(
116            &self.registration.bytes,
117            &root_ref,
118            self.registration.value.device_id,
119        )
120        .map_err(|error| DbError::context("founder Store registration", error))?;
121        if registration != self.registration.value
122            || registration.author_pubkey != root.descriptor.founder_pubkey
123            || self.registration.prepared.reference().slot()
124                != &root.descriptor.founder_registration
125            || registration.provider != root.descriptor.founder_provider_admin.provider
126            || !matches!(
127                registration.origin,
128                coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Founder { .. }
129            )
130        {
131            return Err(DbError::Message(
132                "founder registration differs from its root or prepared exact object".to_string(),
133            ));
134        }
135        let registration_ref = StoreDeviceRegistrationRef::from_registration(
136            &registration,
137            self.registration.prepared.reference().clone(),
138        );
139        let initial_ack = StoreAck::parse_at(
140            &self.initial_ack.bytes,
141            &root_ref,
142            &self.initial_ack_ref,
143            &registration,
144        )
145        .map_err(|error| DbError::context("founder initial acknowledgement", error))?;
146        if initial_ack != self.initial_ack.value
147            || self.initial_ack_ref.registration != registration_ref
148            || self.initial_ack_ref.sequence != 1
149            || &self.initial_ack_ref.object != self.initial_ack.prepared.reference()
150            || initial_ack.successor.predecessor.is_some()
151            || initial_ack.registration != registration_ref
152            || !initial_ack.store_cut.0.is_empty()
153        {
154            return Err(DbError::Message(
155                "founder initial acknowledgement differs from its exact root graph".to_string(),
156            ));
157        }
158        {
159            let entry = &self.membership.entry;
160            let entry_ref = &self.membership.entry_ref;
161            let head = &self.membership.head;
162            let head_ref = &self.membership.head_ref;
163            let parsed_entry: MembershipEntry = serde_json::from_slice(&entry.bytes)
164                .map_err(|error| DbError::context("founder membership entry", error))?;
165            if parsed_entry != entry.value
166                || root
167                    .descriptor
168                    .validate_merge_founder_entry(&parsed_entry)
169                    .is_err()
170                || entry_ref.coord != parsed_entry.coord()
171                || &entry_ref.object != entry.prepared.reference()
172            {
173                return Err(DbError::Message(
174                    "founder membership entry differs from its root or exact reference".to_string(),
175                ));
176            }
177            let parsed_head: AuthorHead = serde_json::from_slice(&head.bytes)
178                .map_err(|error| DbError::context("founder membership head", error))?;
179            let anchor = parsed_entry.change.membership_anchor().ok_or_else(|| {
180                DbError::Message("founder entry has no Store membership anchor".to_string())
181            })?;
182            let coven_protocol::store_commit::GrantStreamAnchor::StoreMembership { first_slot } =
183                anchor
184            else {
185                return Err(DbError::Message(
186                    "founder membership entry uses a recovery anchor".to_string(),
187                ));
188            };
189            if parsed_head != head.value
190                || !parsed_head.verify(&registration)
191                || parsed_head.body.author_registration != registration_ref
192                || parsed_head.body.entry != *entry_ref
193                || parsed_head.body.predecessor.is_some()
194                || parsed_head.entry_coord() != parsed_entry.coord()
195                || head_ref.coord != parsed_entry.coord()
196                || head_ref.head_hash != parsed_head.head_hash()
197                || &head_ref.object != head.prepared.reference()
198                || head.prepared.reference().slot() != &first_slot
199                || parsed_head.body.successor.activation
200                    != coven_protocol::store_commit::StreamActivation::grant_authorized(
201                        root_ref.store_root_hash,
202                        registration_ref.clone(),
203                        parsed_entry.author_owner_grant.clone(),
204                        coven_protocol::store_commit::GrantStreamAnchor::StoreMembership {
205                            first_slot: first_slot.clone(),
206                        },
207                    )
208                    .activation_id()
209            {
210                return Err(DbError::Message(
211                    "founder membership head differs from its exact root graph".to_string(),
212                ));
213            }
214        }
215        Ok(())
216    }
217}
218
219#[derive(Debug, Clone)]
220pub struct DurableFounderMembership {
221    pub entry: ExactProtocolObject<MembershipEntry>,
222    pub entry_ref: MembershipEntryRef,
223    pub head: ExactProtocolObject<AuthorHead>,
224    pub head_ref: MembershipHeadRef,
225}
226
227#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
228#[serde(deny_unknown_fields)]
229pub struct DurableFounderMembershipJournal {
230    entry_ref: MembershipEntryRef,
231    entry_bytes: Vec<u8>,
232    entry_prepared: PreparedExactObject,
233    head_ref: MembershipHeadRef,
234    head_bytes: Vec<u8>,
235    head_prepared: PreparedExactObject,
236}
237
238impl DurableFounderMembershipJournal {
239    pub fn from_graph(graph: &DurableFounderMembership) -> Self {
240        Self {
241            entry_ref: graph.entry_ref.clone(),
242            entry_bytes: graph.entry.bytes.clone(),
243            entry_prepared: graph.entry.prepared.clone(),
244            head_ref: graph.head_ref.clone(),
245            head_bytes: graph.head.bytes.clone(),
246            head_prepared: graph.head.prepared.clone(),
247        }
248    }
249
250    pub fn into_graph(self) -> Result<DurableFounderMembership, DbError> {
251        let entry_value: MembershipEntry = serde_json::from_slice(&self.entry_bytes)
252            .map_err(|error| DbError::context("local founder membership entry", error))?;
253        let head_value: AuthorHead = serde_json::from_slice(&self.head_bytes)
254            .map_err(|error| DbError::context("local founder membership head", error))?;
255        Ok(DurableFounderMembership {
256            entry: ExactProtocolObject {
257                value: entry_value,
258                bytes: self.entry_bytes,
259                prepared: self.entry_prepared,
260            },
261            entry_ref: self.entry_ref,
262            head: ExactProtocolObject {
263                value: head_value,
264                bytes: self.head_bytes,
265                prepared: self.head_prepared,
266            },
267            head_ref: self.head_ref,
268        })
269    }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct FounderMembershipRefs {
274    pub entry: MembershipEntryRef,
275    pub head: MembershipHeadRef,
276}
277
278pub(crate) fn founder_graph_identity(graph: &DurableFounderGraph) -> ObjectHash {
279    let membership = serde_json::to_vec(&(
280        &graph.membership.entry_ref,
281        &graph.membership.entry.bytes,
282        &graph.membership.entry.prepared,
283        &graph.membership.head_ref,
284        &graph.membership.head.bytes,
285        &graph.membership.head.prepared,
286    ))
287    .expect("founder membership graph serialization cannot fail");
288    ObjectHash::digest(
289        &serde_json::to_vec(&(
290            &graph.root.bytes,
291            &graph.root.prepared,
292            &graph.registration.bytes,
293            &graph.registration.prepared,
294            &graph.initial_ack_ref,
295            &graph.initial_ack.bytes,
296            &graph.initial_ack.prepared,
297            membership,
298        ))
299        .expect("founder graph serialization cannot fail"),
300    )
301}
302
303pub(crate) fn load_store_root_authority_on(
304    conn: &Connection,
305) -> Result<
306    Option<(
307        coven_protocol::store_commit::StoreRootRef,
308        StoreProtocolRoot,
309    )>,
310    DbError,
311> {
312    conn.query_row(
313        "SELECT store_root_hash, store_protocol_root_bytes, store_root_object \
314         FROM store_protocol_root_authority WHERE singleton = 1",
315        [],
316        |row| {
317            Ok((
318                row.get::<_, String>(0)?,
319                row.get::<_, Vec<u8>>(1)?,
320                row.get::<_, String>(2)?,
321            ))
322        },
323    )
324    .optional()
325    .map_err(DbError::from)?
326    .map(|(hash, bytes, object)| {
327        let value = StoreProtocolRoot::parse(&bytes)
328            .map_err(|error| DbError::context("Store root authority bytes", error))?;
329        let store_root_hash: ObjectHash = hash
330            .parse()
331            .map_err(|error| DbError::context("Store root authority semantic hash", error))?;
332        let object: ExactObjectRef = serde_json::from_str(&object)
333            .map_err(|error| DbError::context("Store root authority object", error))?;
334        if value.object_hash() != store_root_hash {
335            return Err(DbError::Message(
336                "Store root authority hash differs from its signed bytes".to_string(),
337            ));
338        }
339        Ok((
340            coven_protocol::store_commit::StoreRootRef {
341                store_root_id: value.descriptor.store_root_id(),
342                store_root_hash,
343                object,
344            },
345            value,
346        ))
347    })
348    .transpose()
349}
350
351pub(crate) fn install_store_root_authority_on(
352    conn: &Connection,
353    reference: &coven_protocol::store_commit::StoreRootRef,
354    bytes: &[u8],
355) -> Result<StoreProtocolRoot, DbError> {
356    let value = StoreProtocolRoot::parse(bytes)
357        .map_err(|error| DbError::context("install Store root authority", error))?;
358    if value.object_hash() != reference.store_root_hash {
359        return Err(DbError::Message(
360            "installed Store root reference differs from its signed bytes".to_string(),
361        ));
362    }
363    let object = serde_json::to_string(&reference.object)
364        .map_err(|error| DbError::context("serialize Store root authority", error))?;
365    let existing = load_store_root_authority_on(conn)?;
366    if let Some((existing_reference, existing_value)) = existing {
367        if existing_reference == *reference && existing_value == value {
368            return Ok(value);
369        }
370        return Err(DbError::Message(
371            "database already trusts a different exact Store root".to_string(),
372        ));
373    }
374    conn.execute(
375        "INSERT INTO store_protocol_root_authority \
376         (singleton, store_root_hash, store_protocol_root_bytes, store_root_object) \
377         VALUES (1, ?1, ?2, ?3)",
378        rusqlite::params![reference.store_root_hash.to_string(), bytes, object],
379    )
380    .map_err(DbError::from)?;
381    Ok(value)
382}
383
384pub(crate) fn validate_replay_authority_on(
385    conn: &Connection,
386    baseline: &RetainedReplayBaseline,
387) -> Result<(), DbError> {
388    let (root_ref, root) = load_store_root_authority_on(conn)?.ok_or_else(|| {
389        DbError::Message("retained replay image has no Store root authority".to_string())
390    })?;
391    let (authority_root, founder_registration) = match &baseline.authority {
392        RetainedReplayAuthority::Genesis(authority) => {
393            (&authority.store_root, &authority.founder_registration)
394        }
395        RetainedReplayAuthority::InstalledSnapshot(authority) => {
396            authority.validate()?;
397            (&authority.store_root, &authority.founder_registration)
398        }
399    };
400    if &root_ref != authority_root || root.descriptor.sync_routing_hash != baseline.routing_hash {
401        return Err(DbError::Message(
402            "retained replay authority differs from its Store root".to_string(),
403        ));
404    }
405    let founder = load_activated_registration_on(conn, &root_ref, founder_registration)?;
406    let authority: String = conn
407        .query_row(
408            "SELECT activation_authority
409             FROM store_device_registration_activations
410             WHERE device_id = ?1 AND registration_hash = ?2",
411            (
412                founder_registration.device_id.to_string(),
413                founder_registration.registration_hash.to_string(),
414            ),
415            |row| row.get(0),
416        )
417        .map_err(DbError::from)?;
418    let authority: coven_protocol::store_commit::StoreDeviceRegistrationActivation =
419        serde_json::from_str(&authority).map_err(|error| {
420            DbError::context("retained replay founder activation authority", error)
421        })?;
422    if founder.store_root != root_ref
423        || authority
424            != (coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
425                root: root_ref.clone(),
426            })
427    {
428        return Err(DbError::Message(
429            "retained replay founder differs from its exact activation".to_string(),
430        ));
431    }
432    if let RetainedReplayAuthority::InstalledSnapshot(authority) = &baseline.authority {
433        for registration in authority.active_registrations.values() {
434            let installed =
435                load_activated_registration_on(conn, &root_ref, registration.reference())?;
436            if &installed != registration.value() {
437                return Err(DbError::Message(
438                    "retained snapshot active registration differs from its installed authority"
439                        .to_string(),
440                ));
441            }
442        }
443    }
444    Ok(())
445}
446
447pub(crate) fn install_store_founder_state_on(
448    conn: &Connection,
449    root: &coven_protocol::store_commit::StoreRootRef,
450    founder_reference: &StoreDeviceRegistrationRef,
451    founder: &StoreDeviceRegistration,
452    founder_bytes: &[u8],
453    genesis: &ResolvedStoreDeviceState,
454) -> Result<(), DbError> {
455    if founder.store_root != *root {
456        return Err(DbError::Message(
457            "Store founder registration belongs to another exact root".to_string(),
458        ));
459    }
460    founder_reference
461        .verify_registration(founder)
462        .map_err(DbError::from)?;
463    if founder.to_bytes() != founder_bytes {
464        return Err(DbError::Message(
465            "Store founder registration differs from its exact bytes".to_string(),
466        ));
467    }
468    let founder_authority =
469        coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
470            root: root.clone(),
471        };
472    let founder_values = (
473        founder_reference.registration_hash.to_string(),
474        founder.author_pubkey.clone(),
475        founder.device_signing_pubkey.clone(),
476        founder_bytes.to_vec(),
477        serde_json::to_string(founder_reference)
478            .map_err(|error| DbError::context("serialize Store founder registration ref", error))?,
479        serde_json::to_string(&founder_authority)
480            .map_err(|error| DbError::context("serialize Store founder activation", error))?,
481    );
482    conn.execute(
483        "INSERT INTO store_device_registration_activations
484         (device_id, registration_hash, author_pubkey, device_signing_pubkey,
485          registration_bytes, registration_object, activation_authority)
486         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
487         ON CONFLICT(device_id) DO NOTHING",
488        rusqlite::params![
489            founder.device_id.to_string(),
490            &founder_values.0,
491            &founder_values.1,
492            &founder_values.2,
493            &founder_values.3,
494            &founder_values.4,
495            &founder_values.5,
496        ],
497    )
498    .map_err(DbError::from)?;
499    let stored_founder: (String, String, String, Vec<u8>, String, String) = conn
500        .query_row(
501            "SELECT registration_hash, author_pubkey, device_signing_pubkey,
502                    registration_bytes, registration_object, activation_authority
503             FROM store_device_registration_activations WHERE device_id = ?1",
504            [founder.device_id.to_string()],
505            |row| {
506                Ok((
507                    row.get(0)?,
508                    row.get(1)?,
509                    row.get(2)?,
510                    row.get(3)?,
511                    row.get(4)?,
512                    row.get(5)?,
513                ))
514            },
515        )
516        .map_err(DbError::from)?;
517    if stored_founder != founder_values {
518        return Err(DbError::Message(
519            "Store founder activation differs from installed exact authority".to_string(),
520        ));
521    }
522    let genesis = serde_json::to_string(genesis)
523        .map_err(|error| DbError::context("serialize Store device genesis", error))?;
524    conn.execute(
525        "INSERT OR IGNORE INTO protocol_state (key, value) VALUES (?1, ?2)",
526        (STORE_DEVICE_GENESIS_STATE_KEY, &genesis),
527    )
528    .map_err(DbError::from)?;
529    let stored_genesis = required_protocol_state_on(conn, STORE_DEVICE_GENESIS_STATE_KEY)?;
530    if stored_genesis != genesis {
531        return Err(DbError::Message(
532            "Store device genesis differs from installed exact authority".to_string(),
533        ));
534    }
535    Ok(())
536}
537
538pub(crate) fn load_local_store_founder_graph_on(
539    conn: &Connection,
540) -> Result<Option<Box<DurableFounderGraph>>, DbError> {
541    let owned_rows: i64 = conn
542        .query_row(
543            "SELECT EXISTS(SELECT 1 FROM local_store_protocol_root) \
544                  + EXISTS(SELECT 1 FROM local_store_device_registration) \
545                  + EXISTS(SELECT 1 FROM local_store_founder_graph)",
546            [],
547            |row| row.get(0),
548        )
549        .map_err(DbError::from)?;
550    if owned_rows == 0 {
551        return Ok(None);
552    }
553    if owned_rows != 3 {
554        return Err(DbError::Message(
555            "local Store founder graph is only partially durable".to_string(),
556        ));
557    }
558    let raw = conn
559        .query_row(
560            "SELECT r.store_root_hash, r.store_protocol_root_bytes, r.prepared_object, \
561                    d.device_id, d.registration_hash, d.registration_bytes, d.prepared_object, \
562                    d.initial_ack_ref, d.initial_ack_bytes, d.initial_ack_prepared, d.state, \
563                    g.membership_graph \
564             FROM local_store_protocol_root r \
565             CROSS JOIN local_store_device_registration d \
566             CROSS JOIN local_store_founder_graph g \
567             WHERE r.singleton = 1 AND d.singleton = 1 AND g.singleton = 1",
568            [],
569            |row| {
570                Ok((
571                    row.get::<_, String>(0)?,
572                    row.get::<_, Vec<u8>>(1)?,
573                    row.get::<_, String>(2)?,
574                    row.get::<_, String>(3)?,
575                    row.get::<_, String>(4)?,
576                    row.get::<_, Vec<u8>>(5)?,
577                    row.get::<_, String>(6)?,
578                    row.get::<_, String>(7)?,
579                    row.get::<_, Vec<u8>>(8)?,
580                    row.get::<_, String>(9)?,
581                    row.get::<_, String>(10)?,
582                    row.get::<_, String>(11)?,
583                ))
584            },
585        )
586        .map_err(DbError::from)?;
587    let (
588        root_hash,
589        root_bytes,
590        root_prepared,
591        device_id,
592        registration_hash,
593        registration_bytes,
594        registration_prepared,
595        initial_ack_ref,
596        initial_ack_bytes,
597        initial_ack_prepared,
598        registration_state,
599        membership_graph,
600    ) = raw;
601    let registration_state: LocalDeviceRegistrationState =
602        serde_json::from_str(&registration_state)
603            .map_err(|error| DbError::context("local registration journal state", error))?;
604    let root_value = StoreProtocolRoot::parse(&root_bytes)
605        .map_err(|error| DbError::context("local founder Store root", error))?;
606    let root_prepared: PreparedExactObject = serde_json::from_str(&root_prepared)
607        .map_err(|error| DbError::context("local founder Store root object", error))?;
608    let store_root_hash: ObjectHash = root_hash
609        .parse()
610        .map_err(|error| DbError::context("local founder Store root hash", error))?;
611    if store_root_hash != root_value.object_hash() {
612        return Err(DbError::Message(
613            "local founder Store root hash differs from its bytes".to_string(),
614        ));
615    }
616    let root_ref = coven_protocol::store_commit::StoreRootRef {
617        store_root_id: root_value.descriptor.store_root_id(),
618        store_root_hash,
619        object: root_prepared.reference().clone(),
620    };
621    let parsed_device_id = device_id
622        .parse()
623        .map_err(|error| DbError::context("local founder device id", error))?;
624    let registration_value =
625        StoreDeviceRegistration::parse_at(&registration_bytes, &root_ref, parsed_device_id)
626            .map_err(|error| DbError::context("local founder Store registration", error))?;
627    let parsed_registration_hash: ObjectHash = registration_hash
628        .parse()
629        .map_err(|error| DbError::context("local founder Store registration hash", error))?;
630    if parsed_registration_hash != registration_value.registration_hash() {
631        return Err(DbError::Message(
632            "local founder registration hash differs from its bytes".to_string(),
633        ));
634    }
635    let registration_prepared: PreparedExactObject =
636        serde_json::from_str(&registration_prepared)
637            .map_err(|error| DbError::context("local founder registration object", error))?;
638    let initial_ack_ref: StoreAckRef = serde_json::from_str(&initial_ack_ref)
639        .map_err(|error| DbError::context("local founder initial ack ref", error))?;
640    let initial_ack_value = StoreAck::parse_at(
641        &initial_ack_bytes,
642        &root_ref,
643        &initial_ack_ref,
644        &registration_value,
645    )
646    .map_err(|error| DbError::context("local founder initial ack", error))?;
647    let initial_ack_prepared: PreparedExactObject = serde_json::from_str(&initial_ack_prepared)
648        .map_err(|error| DbError::context("local founder initial ack object", error))?;
649    let membership = serde_json::from_str::<DurableFounderMembershipJournal>(&membership_graph)
650        .map_err(|error| DbError::context("local founder membership graph", error))?
651        .into_graph()?;
652    let graph = DurableFounderGraph {
653        root: ExactProtocolObject {
654            value: root_value,
655            bytes: root_bytes,
656            prepared: root_prepared,
657        },
658        registration: ExactProtocolObject {
659            value: registration_value,
660            bytes: registration_bytes,
661            prepared: registration_prepared,
662        },
663        initial_ack: ExactProtocolObject {
664            value: initial_ack_value,
665            bytes: initial_ack_bytes,
666            prepared: initial_ack_prepared,
667        },
668        initial_ack_ref,
669        membership,
670        registration_state,
671    };
672    graph.validate()?;
673    Ok(Some(Box::new(graph)))
674}