1use crate::*;
2use coven_protocol::store_commit::{StoreAck, StoreDeviceRegistration, StoreDeviceRegistrationRef};
3use rusqlite::OptionalExtension;
4
5use super::*;
6
7pub(crate) struct LocalRegistrationRecord {
14 registration: ExactProtocolObject<StoreDeviceRegistration>,
15 initial_ack_ref: StoreAckRef,
16 initial_ack: ExactProtocolObject<StoreAck>,
17 reference: StoreDeviceRegistrationRef,
18}
19
20impl LocalRegistrationRecord {
21 pub(crate) fn checked(
25 registration: ExactProtocolObject<StoreDeviceRegistration>,
26 initial_ack_ref: StoreAckRef,
27 initial_ack: ExactProtocolObject<StoreAck>,
28 subject: &str,
29 ) -> Result<Self, DbError> {
30 let reference = StoreDeviceRegistrationRef::from_registration(
31 ®istration.value,
32 registration.prepared.reference().clone(),
33 );
34 if registration.value.to_bytes() != registration.bytes
35 || initial_ack.value.to_bytes() != initial_ack.bytes
36 || &initial_ack_ref.object != initial_ack.prepared.reference()
37 || initial_ack_ref.ack_hash != initial_ack.value.ack_hash()
38 || initial_ack_ref.registration != reference
39 || initial_ack_ref.sequence != initial_ack.value.sequence
40 || initial_ack.value.registration != reference
41 {
42 return Err(DbError::Message(format!(
43 "{subject} contains mismatched exact objects"
44 )));
45 }
46 Ok(Self {
47 registration,
48 initial_ack_ref,
49 initial_ack,
50 reference,
51 })
52 }
53
54 pub(crate) fn checked_at_stream_start(
57 registration: ExactProtocolObject<StoreDeviceRegistration>,
58 initial_ack_ref: StoreAckRef,
59 initial_ack: ExactProtocolObject<StoreAck>,
60 subject: &str,
61 ) -> Result<Self, DbError> {
62 let record = Self::checked(registration, initial_ack_ref, initial_ack, subject)?;
63 if record.initial_ack_ref.sequence != 1
64 || record.initial_ack.value.successor.predecessor.is_some()
65 {
66 return Err(DbError::Message(format!(
67 "{subject} does not start its acknowledgement stream"
68 )));
69 }
70 Ok(record)
71 }
72
73 pub(crate) fn reference(&self) -> &StoreDeviceRegistrationRef {
74 &self.reference
75 }
76
77 pub(super) fn checked_owner_recovery(
78 registration: ExactProtocolObject<StoreDeviceRegistration>,
79 initial_ack_ref: StoreAckRef,
80 initial_ack: ExactProtocolObject<StoreAck>,
81 activation: &coven_protocol::store_commit::StoreDeviceRegistrationActivation,
82 subject: &str,
83 ) -> Result<Self, DbError> {
84 let (
85 coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Recovery {
86 recovery_id: origin_recovery_id,
87 recovery_slot,
88 owner_grant,
89 ..
90 },
91 coven_protocol::store_commit::StoreDeviceRegistrationActivation::Recovery {
92 recovery_id: activation_recovery_id,
93 node,
94 },
95 ) = (®istration.value.origin, activation)
96 else {
97 return Err(DbError::Message(
98 "Owner recovery journal requires one Recovery registration authority".into(),
99 ));
100 };
101 if origin_recovery_id != activation_recovery_id
102 || node.object.slot() != recovery_slot
103 || node.owner_grant != *owner_grant
104 {
105 return Err(DbError::Message(
106 "Owner recovery registration differs from its activation authority".into(),
107 ));
108 }
109 Self::checked_at_stream_start(registration, initial_ack_ref, initial_ack, subject)
110 }
111
112 pub(super) fn initial_ack_ref(&self) -> &StoreAckRef {
113 &self.initial_ack_ref
114 }
115
116 pub(super) fn replace_journal_on(
117 &self,
118 tx: &rusqlite::Transaction<'_>,
119 state: LocalDeviceRegistrationState,
120 subject: &str,
121 ) -> Result<(), DbError> {
122 if let Some(active) = super::active_store_publication::load_active_store_publication_on(tx)?
126 {
127 if active.author_registration() != self.reference() {
128 return Err(DbError::Message(format!(
129 "cannot replace local Store registration while {:?} owns publication",
130 active.owner()
131 )));
132 }
133 }
134 let objects = self.columns(subject)?;
135 let state = encode(&state, subject, "journal state")?;
136 tx.execute("DELETE FROM local_store_device_registration", [])?;
137 tx.execute(
138 "INSERT INTO local_store_device_registration \
139 (singleton, device_id, registration_hash, registration_bytes, \
140 prepared_object, initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state) \
141 VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
142 rusqlite::params![
143 objects.0, objects.1, objects.2, objects.3, objects.4, objects.5, objects.6, state,
144 ],
145 )?;
146 Ok(())
147 }
148
149 pub(crate) fn registration(&self) -> &StoreDeviceRegistration {
150 &self.registration.value
151 }
152
153 pub(crate) fn device_id(&self) -> String {
154 self.reference.device_id.to_string()
155 }
156
157 pub(crate) fn require_installed_store_root(
160 &self,
161 root: &coven_protocol::store_commit::StoreRootRef,
162 subject: &str,
163 ) -> Result<(), DbError> {
164 if &self.registration.value.store_root != root {
165 return Err(DbError::Message(format!(
166 "{subject} belongs to another Store root"
167 )));
168 }
169 Ok(())
170 }
171
172 pub(crate) fn columns(
174 &self,
175 subject: &str,
176 ) -> Result<PreparedLocalDeviceRegistrationRow, DbError> {
177 Ok((
178 self.device_id(),
179 self.reference.registration_hash.to_string(),
180 self.registration.bytes.clone(),
181 encode(&self.registration.prepared, subject, "registration object")?,
182 encode(&self.initial_ack_ref, subject, "acknowledgement ref")?,
183 self.initial_ack.bytes.clone(),
184 encode(
185 &self.initial_ack.prepared,
186 subject,
187 "acknowledgement object",
188 )?,
189 ))
190 }
191
192 pub(crate) fn published_ack_columns(&self, subject: &str) -> Result<(String, String), DbError> {
195 Ok((
196 encode(&self.initial_ack_ref, subject, "acknowledgement ref")?,
197 encode(
198 &self.initial_ack.value.successor.next_slot,
199 subject,
200 "acknowledgement successor",
201 )?,
202 ))
203 }
204}
205
206fn encode<T: serde::Serialize>(value: &T, subject: &str, what: &str) -> Result<String, DbError> {
207 serde_json::to_string(value)
208 .map_err(|error| DbError::context(format!("serialize {subject} {what}"), error))
209}
210
211impl StoreSession<'_> {
212 fn stage_local_store_device_registration(
213 &mut self,
214 record: LocalRegistrationRecord,
215 initial_state: LocalDeviceRegistrationState,
216 subject: &str,
217 ) -> Result<(), DbError> {
218 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
219 let root = self
220 .verified_store_authority
221 .required_root_authority_on(records)?;
222 record.require_installed_store_root(&root, subject)?;
223 let expected = record.columns(subject)?;
224 let existing: Option<PreparedLocalDeviceRegistrationRow> = self
225 .conn
226 .query_row(
227 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
228 initial_ack_ref, initial_ack_bytes, initial_ack_prepared \
229 FROM local_store_device_registration WHERE singleton = 1",
230 [],
231 |row| {
232 Ok((
233 row.get(0)?,
234 row.get(1)?,
235 row.get(2)?,
236 row.get(3)?,
237 row.get(4)?,
238 row.get(5)?,
239 row.get(6)?,
240 ))
241 },
242 )
243 .optional()
244 .map_err(DbError::from)?;
245 match existing {
246 Some(existing) if existing == expected => {
247 let state: String = self
248 .conn
249 .query_row(
250 "SELECT state FROM local_store_device_registration WHERE singleton = 1",
251 [],
252 |row| row.get(0),
253 )
254 .map_err(DbError::from)?;
255 let state: LocalDeviceRegistrationState = serde_json::from_str(&state)
256 .map_err(|error| DbError::context("parse local registration state", error))?;
257 let valid = match (&initial_state, &state) {
258 (LocalDeviceRegistrationState::Prepared, _) => true,
259 (
260 LocalDeviceRegistrationState::RegistrationActivated {
261 authority: expected,
262 },
263 LocalDeviceRegistrationState::RegistrationActivated { authority: actual }
264 | LocalDeviceRegistrationState::Activated { authority: actual },
265 ) => expected == actual,
266 _ => false,
267 };
268 if !valid {
269 return Err(DbError::Message(
270 "local registration journal has a different publication state".to_string(),
271 ));
272 }
273 Ok(())
274 }
275 Some(_) => Err(DbError::Message(
276 "local registration journal already owns different exact objects".to_string(),
277 )),
278 None => self
279 .conn
280 .execute(
281 "INSERT INTO local_store_device_registration \
282 (singleton, device_id, registration_hash, registration_bytes, \
283 prepared_object, initial_ack_ref, initial_ack_bytes, \
284 initial_ack_prepared, state) \
285 VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
286 rusqlite::params![
287 expected.0,
288 expected.1,
289 expected.2,
290 expected.3,
291 expected.4,
292 expected.5,
293 expected.6,
294 serde_json::to_string(&initial_state).map_err(|error| {
295 DbError::context("serialize local registration state", error)
296 })?,
297 ],
298 )
299 .map(|_| ())
300 .map_err(DbError::from),
301 }
302 }
303
304 fn stage_activated_local_store_device_registration(
305 &mut self,
306 record: LocalRegistrationRecord,
307 authority: coven_protocol::store_commit::StoreDeviceRegistrationActivation,
308 subject: &str,
309 ) -> Result<(), DbError> {
310 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
311 let root = self
312 .verified_store_authority
313 .required_root_authority_on(records)?;
314 record.require_installed_store_root(&root, subject)?;
315 let installed: (String, Vec<u8>, String, String) = self
316 .conn
317 .query_row(
318 "SELECT registration_hash, registration_bytes, registration_object, \
319 activation_authority \
320 FROM store_device_registration_activations WHERE device_id = ?1",
321 [record.device_id()],
322 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
323 )
324 .map_err(DbError::from)?;
325 let expected = (
326 record.reference().registration_hash.to_string(),
327 record.registration.bytes.clone(),
328 encode(record.reference(), subject, "registration ref")?,
329 encode(&authority, subject, "activation authority")?,
330 );
331 if installed != expected {
332 return Err(DbError::Message(
333 "installed activation differs from the local registration graph".to_string(),
334 ));
335 }
336 self.stage_local_store_device_registration(
337 record,
338 LocalDeviceRegistrationState::RegistrationActivated { authority },
339 subject,
340 )
341 }
342
343 fn install_existing_local_founder_device(
344 &mut self,
345 record: LocalRegistrationRecord,
346 subject: &str,
347 ) -> Result<(), DbError> {
348 let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
349 let store_transaction =
350 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
351 let root = store_transaction.required_root_authority(self.verified_store_authority)?;
352 record.require_installed_store_root(&root, subject)?;
353 let coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Founder { .. } =
354 &record.registration().origin
355 else {
356 return Err(DbError::Message(
357 "existing local founder device has a non-founder origin".to_string(),
358 ));
359 };
360 let activated = store_transaction.activated_registration(
361 self.verified_store_authority,
362 &root,
363 record.reference(),
364 )?;
365 if activated != *record.registration() {
366 return Err(DbError::Message(
367 "existing local founder device differs from its installed activation".to_string(),
368 ));
369 }
370 let authority = coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
371 root: root.clone(),
372 };
373 let objects = record.columns(subject)?;
374 let expected = (
375 objects.0,
376 objects.1,
377 objects.2,
378 objects.3,
379 objects.4,
380 objects.5,
381 objects.6,
382 encode(
383 &LocalDeviceRegistrationState::Activated { authority },
384 subject,
385 "registration state",
386 )?,
387 );
388 tx.execute(
389 "INSERT INTO local_store_device_registration
390 (singleton, device_id, registration_hash, registration_bytes,
391 prepared_object, initial_ack_ref, initial_ack_bytes,
392 initial_ack_prepared, state)
393 VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
394 ON CONFLICT(singleton) DO NOTHING",
395 rusqlite::params![
396 &expected.0,
397 &expected.1,
398 &expected.2,
399 &expected.3,
400 &expected.4,
401 &expected.5,
402 &expected.6,
403 &expected.7,
404 ],
405 )
406 .map_err(DbError::from)?;
407 let stored: LocalDeviceRegistrationJournalRow = tx
408 .query_row(
409 "SELECT device_id, registration_hash, registration_bytes, prepared_object,
410 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state
411 FROM local_store_device_registration WHERE singleton = 1",
412 [],
413 |row| {
414 Ok((
415 row.get(0)?,
416 row.get(1)?,
417 row.get(2)?,
418 row.get(3)?,
419 row.get(4)?,
420 row.get(5)?,
421 row.get(6)?,
422 row.get(7)?,
423 ))
424 },
425 )
426 .map_err(DbError::from)?;
427 if stored != expected {
428 return Err(DbError::Message(
429 "existing local founder journal owns different exact objects".to_string(),
430 ));
431 }
432 let published_ack = record.published_ack_columns(subject)?;
433 tx.execute(
434 "INSERT INTO published_store_acks (singleton, ack_ref, successor_slot)
435 VALUES (1, ?1, ?2) ON CONFLICT(singleton) DO NOTHING",
436 (&published_ack.0, &published_ack.1),
437 )
438 .map_err(DbError::from)?;
439 let stored_ack: (String, String) = tx
440 .query_row(
441 "SELECT ack_ref, successor_slot FROM published_store_acks WHERE singleton = 1",
442 [],
443 |row| Ok((row.get(0)?, row.get(1)?)),
444 )
445 .map_err(DbError::from)?;
446 if stored_ack != published_ack {
447 return Err(DbError::Message(
448 "existing local founder acknowledgement differs from exact cloud state".to_string(),
449 ));
450 }
451 tx.execute(
452 "INSERT INTO protocol_state (key, value) VALUES (?1, ?2)
453 ON CONFLICT(key) DO NOTHING",
454 (LOCAL_DEVICE_ID_STATE_KEY, &expected.0),
455 )
456 .map_err(DbError::from)?;
457 let stored_device_id = crate::required_protocol_state_on(&tx, LOCAL_DEVICE_ID_STATE_KEY)?;
458 if stored_device_id != expected.0 {
459 return Err(DbError::Message(
460 "existing local founder device id conflicts with installed state".to_string(),
461 ));
462 }
463 tx.commit().map_err(DbError::from)
464 }
465
466 fn stage_owner_recovery_registration(
467 &mut self,
468 record: LocalRegistrationRecord,
469 activation: coven_protocol::store_commit::StoreDeviceRegistrationActivation,
470 subject: &str,
471 ) -> Result<bool, DbError> {
472 let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
473 let store_transaction =
474 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
475 let root = store_transaction.required_root_authority(self.verified_store_authority)?;
476 record.require_installed_store_root(&root, subject)?;
477 let objects = record.columns(subject)?;
478 let exact_registration_ref = encode(record.reference(), subject, "registration ref")?;
479 let exact_activation = encode(&activation, subject, "activation authority")?;
480 let installed = tx
481 .query_row(
482 "SELECT registration_hash, registration_bytes, registration_object, \
483 activation_authority \
484 FROM store_device_registration_activations WHERE device_id = ?1",
485 [record.device_id()],
486 |row| {
487 Ok((
488 row.get::<_, String>(0)?,
489 row.get::<_, Vec<u8>>(1)?,
490 row.get::<_, String>(2)?,
491 row.get::<_, String>(3)?,
492 ))
493 },
494 )
495 .optional()
496 .map_err(DbError::from)?;
497 let activated = match installed {
498 None => false,
499 Some(existing)
500 if existing
501 == (
502 objects.1.clone(),
503 objects.2.clone(),
504 exact_registration_ref,
505 exact_activation,
506 ) =>
507 {
508 true
509 }
510 Some(_) => {
511 return Err(DbError::Message(
512 "Owner recovery device already has different exact activation authority".into(),
513 ));
514 }
515 };
516 if activated {
517 tx.commit().map_err(DbError::from)?;
520 return Ok(true);
521 }
522 let existing: Option<LocalDeviceRegistrationJournalRow> = tx
523 .query_row(
524 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
525 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state \
526 FROM local_store_device_registration WHERE singleton = 1",
527 [],
528 |row| {
529 Ok((
530 row.get(0)?,
531 row.get(1)?,
532 row.get(2)?,
533 row.get(3)?,
534 row.get(4)?,
535 row.get(5)?,
536 row.get(6)?,
537 row.get(7)?,
538 ))
539 },
540 )
541 .optional()
542 .map_err(DbError::from)?;
543 if let Some(existing) = existing.as_ref() {
544 let same_objects = existing.0 == objects.0
545 && existing.1 == objects.1
546 && existing.2 == objects.2
547 && existing.3 == objects.3
548 && existing.4 == objects.4
549 && existing.5 == objects.5
550 && existing.6 == objects.6;
551 if same_objects {
552 let state: LocalDeviceRegistrationState = serde_json::from_str(&existing.7)
553 .map_err(|error| {
554 DbError::context("parse Owner recovery journal state", error)
555 })?;
556 if !matches!(
557 state,
558 LocalDeviceRegistrationState::Prepared | LocalDeviceRegistrationState::Created
559 ) {
560 return Err(DbError::Message(
561 "Owner recovery journal claims activation absent from Store authority"
562 .into(),
563 ));
564 }
565 let published_ack_count: i64 = tx
566 .query_row("SELECT COUNT(*) FROM published_store_acks", [], |row| {
567 row.get(0)
568 })
569 .map_err(DbError::from)?;
570 if published_ack_count != 0
571 || crate::get_protocol_state_on(&tx, LOCAL_DEVICE_ID_STATE_KEY)?.is_some()
572 {
573 return Err(DbError::Message(
574 "unactivated Owner recovery journal has published local authority".into(),
575 ));
576 }
577 tx.commit().map_err(DbError::from)?;
578 return Ok(false);
579 }
580 }
581 tx.execute("DELETE FROM published_store_acks", [])
582 .map_err(DbError::from)?;
583 crate::delete_protocol_state_on(&tx, LOCAL_DEVICE_ID_STATE_KEY)?;
584 record.replace_journal_on(&tx, LocalDeviceRegistrationState::Prepared, subject)?;
585 tx.commit().map_err(DbError::from)?;
586 Ok(false)
587 }
588
589 fn read_local_store_device_registration(
590 &mut self,
591 sql: &'static str,
592 ) -> Result<Option<DurableDeviceRegistration>, DbError> {
593 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
594 self.conn
595 .query_row(sql, [], |row| {
596 Ok((
597 row.get::<_, String>(0)?,
598 row.get::<_, String>(1)?,
599 row.get::<_, Vec<u8>>(2)?,
600 row.get::<_, String>(3)?,
601 row.get::<_, String>(4)?,
602 row.get::<_, Vec<u8>>(5)?,
603 row.get::<_, String>(6)?,
604 row.get::<_, String>(7)?,
605 ))
606 })
607 .optional()
608 .map_err(DbError::from)?
609 .map(
610 |(device_id, hash, bytes, prepared, ack_ref, ack_bytes, ack_prepared, state)| {
611 let device_id = device_id
612 .parse()
613 .map_err(|error| DbError::context("local Store device id", error))?;
614 let prepared: PreparedExactObject =
615 serde_json::from_str(&prepared).map_err(|error| {
616 DbError::context(
617 "local Store device registration prepared object",
618 error,
619 )
620 })?;
621 let registration = StoreDeviceRegistration::parse_at(
622 &bytes,
623 &self
624 .verified_store_authority
625 .required_root_authority_on(records)?,
626 device_id,
627 )
628 .map_err(|error| DbError::context("local Store device registration", error))?;
629 let initial_ack_ref: StoreAckRef =
630 serde_json::from_str(&ack_ref).map_err(|error| {
631 DbError::context("local Store initial acknowledgement ref", error)
632 })?;
633 let initial_ack_value = StoreAck::parse_at(
634 &ack_bytes,
635 ®istration.store_root,
636 &initial_ack_ref,
637 ®istration,
638 )
639 .map_err(|error| {
640 DbError::context("local Store initial acknowledgement", error)
641 })?;
642 let initial_ack_prepared: PreparedExactObject =
643 serde_json::from_str(&ack_prepared).map_err(|error| {
644 DbError::context("local Store initial ack object", error)
645 })?;
646 Ok(DurableDeviceRegistration {
647 device_id,
648 registration_hash: hash.parse().map_err(|error| {
649 DbError::context("local Store device registration hash", error)
650 })?,
651 registration_bytes: bytes,
652 prepared,
653 initial_ack_ref,
654 initial_ack: ExactProtocolObject {
655 value: initial_ack_value,
656 bytes: ack_bytes,
657 prepared: initial_ack_prepared,
658 },
659 state: serde_json::from_str(&state).map_err(|error| {
660 DbError::context("local Store registration journal state", error)
661 })?,
662 })
663 },
664 )
665 .transpose()
666 }
667
668 pub(super) fn local_store_device_registration(
669 &mut self,
670 ) -> Result<Option<DurableDeviceRegistration>, DbError> {
671 self.read_local_store_device_registration(
672 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
673 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state \
674 FROM local_store_device_registration WHERE singleton = 1",
675 )
676 }
677
678 fn local_registration_state(
679 tx: &rusqlite::Transaction<'_>,
680 record: &LocalRegistrationRecord,
681 subject: &str,
682 ) -> Result<LocalDeviceRegistrationState, DbError> {
683 let expected = record.columns(subject)?;
684 let durable: LocalDeviceRegistrationJournalRow = tx
685 .query_row(
686 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
687 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state \
688 FROM local_store_device_registration WHERE singleton = 1",
689 [],
690 |row| {
691 Ok((
692 row.get(0)?,
693 row.get(1)?,
694 row.get(2)?,
695 row.get(3)?,
696 row.get(4)?,
697 row.get(5)?,
698 row.get(6)?,
699 row.get(7)?,
700 ))
701 },
702 )
703 .map_err(DbError::from)?;
704 if durable.0 != expected.0
705 || durable.1 != expected.1
706 || durable.2 != expected.2
707 || durable.3 != expected.3
708 || durable.4 != expected.4
709 || durable.5 != expected.5
710 || durable.6 != expected.6
711 {
712 return Err(DbError::Message(format!(
713 "{subject} differs from its durable exact objects"
714 )));
715 }
716 serde_json::from_str(&durable.7)
717 .map_err(|error| DbError::context(format!("parse {subject} state"), error))
718 }
719
720 fn mark_local_store_device_registration_published(
721 &mut self,
722 record: LocalRegistrationRecord,
723 subject: &str,
724 ) -> Result<(), DbError> {
725 let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
726 let state = Self::local_registration_state(&tx, &record, subject)?;
727 match state {
728 LocalDeviceRegistrationState::Prepared => {
729 tx.execute(
730 "UPDATE local_store_device_registration SET state = ?1 \
731 WHERE singleton = 1 AND state = ?2",
732 rusqlite::params![
733 encode(
734 &LocalDeviceRegistrationState::RegistrationPublished,
735 subject,
736 "published state",
737 )?,
738 encode(
739 &LocalDeviceRegistrationState::Prepared,
740 subject,
741 "prepared state",
742 )?,
743 ],
744 )
745 .map_err(DbError::from)?;
746 }
747 LocalDeviceRegistrationState::RegistrationPublished
748 | LocalDeviceRegistrationState::Created
749 | LocalDeviceRegistrationState::Activated { .. } => {}
750 LocalDeviceRegistrationState::RegistrationActivated { .. } => {
751 return Err(DbError::Message(
752 "activated registration cannot pass through unactivated publication"
753 .to_string(),
754 ));
755 }
756 }
757 tx.commit().map_err(DbError::from)
758 }
759
760 fn mark_local_store_device_ack_published(
761 &mut self,
762 record: LocalRegistrationRecord,
763 subject: &str,
764 ) -> Result<(), DbError> {
765 let tx = self.conn.unchecked_transaction().map_err(DbError::from)?;
766 let state = Self::local_registration_state(&tx, &record, subject)?;
767 let target = match state {
768 LocalDeviceRegistrationState::Prepared
769 | LocalDeviceRegistrationState::RegistrationPublished => {
770 LocalDeviceRegistrationState::Created
771 }
772 LocalDeviceRegistrationState::RegistrationActivated { ref authority } => {
773 let published_ack = record.published_ack_columns(subject)?;
774 tx.execute(
775 "INSERT INTO published_store_acks (singleton, ack_ref, successor_slot) \
776 VALUES (1, ?1, ?2) ON CONFLICT(singleton) DO NOTHING",
777 (&published_ack.0, &published_ack.1),
778 )
779 .map_err(DbError::from)?;
780 let stored_ack: (String, String) = tx
781 .query_row(
782 "SELECT ack_ref, successor_slot FROM published_store_acks \
783 WHERE singleton = 1",
784 [],
785 |row| Ok((row.get(0)?, row.get(1)?)),
786 )
787 .map_err(DbError::from)?;
788 if stored_ack != published_ack {
789 return Err(DbError::Message(
790 "activated local acknowledgement differs from its exact cloud object"
791 .to_string(),
792 ));
793 }
794 crate::set_protocol_state_on(&tx, LOCAL_DEVICE_ID_STATE_KEY, &record.device_id())?;
795 LocalDeviceRegistrationState::Activated {
796 authority: authority.clone(),
797 }
798 }
799 LocalDeviceRegistrationState::Created
800 | LocalDeviceRegistrationState::Activated { .. } => {
801 tx.commit().map_err(DbError::from)?;
802 return Ok(());
803 }
804 };
805 let current = encode(&state, subject, "current state")?;
806 let updated = tx
807 .execute(
808 "UPDATE local_store_device_registration SET state = ?1 \
809 WHERE singleton = 1 AND state = ?2",
810 rusqlite::params![encode(&target, subject, "published state")?, current],
811 )
812 .map_err(DbError::from)?;
813 if updated != 1 {
814 return Err(DbError::Message(
815 "local registration journal changed during acknowledgement publication".to_string(),
816 ));
817 }
818 tx.commit().map_err(DbError::from)
819 }
820}
821
822impl StoreDatabase {
823 pub async fn stage_local_store_device_registration(
824 &self,
825 registration: ExactProtocolObject<StoreDeviceRegistration>,
826 initial_ack_ref: StoreAckRef,
827 initial_ack: ExactProtocolObject<StoreAck>,
828 ) -> Result<(), DbError> {
829 const SUBJECT: &str = "local registration staging graph";
830 let record =
831 LocalRegistrationRecord::checked(registration, initial_ack_ref, initial_ack, SUBJECT)?;
832 self.call_store(move |session| {
833 session.stage_local_store_device_registration(
834 record,
835 LocalDeviceRegistrationState::Prepared,
836 SUBJECT,
837 )
838 })
839 .await
840 }
841
842 pub async fn stage_activated_local_store_device_registration(
843 &self,
844 registration: ExactProtocolObject<StoreDeviceRegistration>,
845 initial_ack_ref: StoreAckRef,
846 initial_ack: ExactProtocolObject<StoreAck>,
847 authority: coven_protocol::store_commit::StoreDeviceRegistrationActivation,
848 ) -> Result<(), DbError> {
849 const SUBJECT: &str = "activated local registration staging graph";
850 let record =
851 LocalRegistrationRecord::checked(registration, initial_ack_ref, initial_ack, SUBJECT)?;
852 self.call_store(move |session| {
853 session.stage_activated_local_store_device_registration(record, authority, SUBJECT)
854 })
855 .await
856 }
857
858 pub async fn install_existing_local_founder_device(
859 &self,
860 registration: ExactProtocolObject<StoreDeviceRegistration>,
861 initial_ack_ref: StoreAckRef,
862 initial_ack: ExactProtocolObject<StoreAck>,
863 ) -> Result<(), DbError> {
864 const SUBJECT: &str = "existing founder device graph";
865 let record = LocalRegistrationRecord::checked_at_stream_start(
866 registration,
867 initial_ack_ref,
868 initial_ack,
869 SUBJECT,
870 )?;
871 self.call_store(move |session| {
872 session.install_existing_local_founder_device(record, SUBJECT)
873 })
874 .await
875 }
876
877 pub async fn stage_owner_recovery_registration(
878 &self,
879 registration: ExactProtocolObject<StoreDeviceRegistration>,
880 initial_ack_ref: StoreAckRef,
881 initial_ack: ExactProtocolObject<StoreAck>,
882 activation: coven_protocol::store_commit::StoreDeviceRegistrationActivation,
883 ) -> Result<bool, DbError> {
884 const SUBJECT: &str = "Owner recovery registration graph";
885 let record = LocalRegistrationRecord::checked_owner_recovery(
886 registration,
887 initial_ack_ref,
888 initial_ack,
889 &activation,
890 SUBJECT,
891 )?;
892 self.call_store(move |session| {
893 session.stage_owner_recovery_registration(record, activation, SUBJECT)
894 })
895 .await
896 }
897
898 pub async fn oldest_unpublished_store_device_registration(
899 &self,
900 ) -> Result<Option<DurableDeviceRegistration>, DbError> {
901 let registration = self
902 .read_local_store_device_registration(
903 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
904 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state \
905 FROM local_store_device_registration WHERE singleton = 1",
906 )
907 .await?;
908 Ok(registration.filter(|registration| {
909 matches!(
910 registration.state,
911 LocalDeviceRegistrationState::Prepared
912 | LocalDeviceRegistrationState::RegistrationPublished
913 | LocalDeviceRegistrationState::RegistrationActivated { .. }
914 )
915 }))
916 }
917
918 pub async fn read_local_store_device_registration(
919 &self,
920 sql: &'static str,
921 ) -> Result<Option<DurableDeviceRegistration>, DbError> {
922 self.call_store(move |session| session.read_local_store_device_registration(sql))
923 .await
924 }
925
926 pub async fn mark_local_store_device_registration_published(
927 &self,
928 registration: ExactProtocolObject<StoreDeviceRegistration>,
929 initial_ack_ref: StoreAckRef,
930 initial_ack_object: ExactProtocolObject<StoreAck>,
931 ) -> Result<(), DbError> {
932 const SUBJECT: &str = "published local registration graph";
933 let record = LocalRegistrationRecord::checked(
934 registration,
935 initial_ack_ref,
936 initial_ack_object,
937 SUBJECT,
938 )?;
939 self.call_store(move |session| {
940 session.mark_local_store_device_registration_published(record, SUBJECT)
941 })
942 .await
943 }
944
945 pub async fn mark_local_store_device_ack_published(
946 &self,
947 registration: ExactProtocolObject<StoreDeviceRegistration>,
948 initial_ack_ref: StoreAckRef,
949 initial_ack_object: ExactProtocolObject<StoreAck>,
950 ) -> Result<(), DbError> {
951 const SUBJECT: &str = "published local acknowledgement graph";
952 let record = LocalRegistrationRecord::checked(
953 registration,
954 initial_ack_ref,
955 initial_ack_object,
956 SUBJECT,
957 )?;
958 self.call_store(move |session| {
959 session.mark_local_store_device_ack_published(record, SUBJECT)
960 })
961 .await
962 }
963}