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