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 ®istration,
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(®istration).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 ®istration_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 ®istration_hash,
493 graph.registration.value.author_pubkey,
494 graph.registration.value.device_signing_pubkey,
495 graph.registration.bytes,
496 serde_json::to_string(®istration).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}