aven-core 0.1.36

Core library for the Aven local-first task manager
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
//! Bounded signed history, invitation outcomes and vault-clock transactions.
use super::super::peer::{Authentication, RegistrationStatus, request_parts};
use super::*;
use crate::db::{Database, begin_immediate};
use sqlx::SqliteConnection;
use std::sync::Arc;

pub const MAX_INVITATIONS: usize = 128;
pub const MAX_CANDIDATES: usize = 8;
pub(crate) struct Current {
    pub membership: Membership,
    pub evidence: Evidence,
}
/// The verified replay of one stored head. Heads are hash-linked over every
/// record, so an unchanged head means unchanged history; storage integrity
/// behind a cached head is established by the full replay at server start.
#[derive(Clone, Default)]
pub(crate) struct Cache(Arc<std::sync::Mutex<Option<Arc<Current>>>>);
impl Cache {
    fn get(&self, sequence: i64, head: &[u8]) -> Option<Arc<Current>> {
        let cached = self.0.lock().unwrap_or_else(|e| e.into_inner());
        cached
            .as_ref()
            .filter(|c| c.membership.sequence() as i64 == sequence && c.membership.head() == head)
            .cloned()
    }
    fn set(&self, current: Arc<Current>) {
        *self.0.lock().unwrap_or_else(|e| e.into_inner()) = Some(current);
    }
}
/// Replays only when the stored head differs from the cached one; invitation
/// bounds and projections are checked on every call.
pub(crate) async fn current(db: &Database, conn: &mut SqliteConnection) -> Result<Arc<Current>> {
    let (invitations, malformed): (i64, i64) = sqlx::query_as("SELECT count(*),coalesce(sum(CASE WHEN length(declaration)!=280 OR (request IS NOT NULL AND length(request)!=314) OR length(handle)!=32 OR length(inviter)!=32 THEN 1 ELSE 0 END),0) FROM server_membership_invitations")
        .fetch_one(&mut *conn).await?;
    ensure!(
        invitations <= MAX_INVITATIONS as i64 && malformed == 0,
        "error membership-limit"
    );
    let clock: bool = sqlx::query_scalar(
        "SELECT EXISTS(SELECT 1 FROM server_membership_clock WHERE high_water>=0)",
    )
    .fetch_one(&mut *conn)
    .await?;
    ensure!(invitations == 0 || clock, "error enrollment-clock-missing");
    let head: Option<(i64, Vec<u8>)> = sqlx::query_as(
        "SELECT sequence,commitment FROM server_e2ee_membership_head WHERE singleton=1",
    )
    .fetch_optional(&mut *conn)
    .await?;
    let cached = head.and_then(|(sequence, head)| db.membership_cache.get(sequence, &head));
    let current = match cached {
        Some(current) => current,
        None => {
            let current = Arc::new(replay(conn).await?);
            db.membership_cache.set(current.clone());
            current
        }
    };
    let orphans: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_membership_invitations i WHERE admitted_sequence IS NOT NULL AND NOT EXISTS(SELECT 1 FROM server_membership_transitions a WHERE a.handle=i.handle AND a.sequence=i.admitted_sequence))")
        .fetch_one(&mut *conn).await?;
    ensure!(!orphans, "error membership-history-missing");
    let invitations: Vec<(Vec<u8>, bool, bool, bool)> = sqlx::query_as("SELECT inviter,admitted_sequence IS NOT NULL,revoked,expired FROM server_membership_invitations")
        .fetch_all(&mut *conn).await?;
    for (inviter, admitted, revoked, expired) in invitations {
        let inviter: Hash = inviter
            .try_into()
            .map_err(|_| anyhow::anyhow!("error membership-storage"))?;
        ensure!(
            revoked == (!admitted && !current.membership.has_device(inviter))
                && (!revoked || expired),
            "error membership-invitation-projection"
        );
    }
    Ok(current)
}
/// Bound stored byte lengths before materializing any attacker-influenced blobs.
async fn replay(conn: &mut SqliteConnection) -> Result<Current> {
    let sizes: Option<(i64, i64, i64, bool)> = sqlx::query_as("SELECT length(s.genesis),length(p.signed_record),length(p.descriptor),s.genesis_only FROM server_seed_claim s JOIN server_bootstrap_publication p ON p.singleton=s.singleton WHERE s.singleton=1")
        .fetch_optional(&mut *conn).await?;
    let (g, p, d, only) = sizes.context("error enrollment-requires-ready")?;
    ensure!(
        !only
            && g == GENESIS_BYTES as i64
            && p == PUBLICATION_BYTES as i64
            && (0..=crate::sync::bootstrap_format::MAX_DESCRIPTOR_BYTES as i64).contains(&d),
        "error membership-storage"
    );
    let (count, total, largest): (i64, i64, i64) = sqlx::query_as("SELECT count(*),coalesce(sum(length(record) + CASE WHEN handle IS NULL THEN 0 ELSE ? END),0),coalesce(max(length(record)),0) FROM server_membership_transitions")
        .bind((DECLARATION_BYTES + REQUEST_BYTES) as i64).fetch_one(&mut *conn).await?;
    ensure!(
        (0..=MAX_TRANSITIONS as i64).contains(&count)
            && largest <= MAX_RECORD_BYTES as i64
            && total >= 0
            && g + p + d + total <= MAX_CHAIN_BYTES as i64,
        "error membership-limit"
    );
    let (genesis, publication, descriptor): (Vec<u8>, Vec<u8>, Vec<u8>) = sqlx::query_as("SELECT s.genesis,p.signed_record,p.descriptor FROM server_seed_claim s JOIN server_bootstrap_publication p ON p.singleton=s.singleton WHERE s.singleton=1")
        .fetch_one(&mut *conn).await?;
    type Row = (i64, Option<Vec<u8>>, Vec<u8>);
    let rows: Vec<Row> = sqlx::query_as(
        "SELECT sequence,handle,record FROM server_membership_transitions ORDER BY sequence",
    )
    .fetch_all(&mut *conn)
    .await?;
    let mut evidence = Evidence {
        genesis,
        publication,
        descriptor,
        transitions: Vec::with_capacity(rows.len()),
    };
    let mut membership = evidence.verify()?;
    for (seq, handle, record) in rows {
        ensure!(
            seq == membership.sequence() as i64 + 1,
            "error membership-history-missing"
        );
        let (declaration, request) = if let Some(handle) = handle {
            let (declaration, request, inviter, expiry, admitted): (Vec<u8>, Vec<u8>, Vec<u8>, i64, i64) =
                sqlx::query_as("SELECT declaration,request,inviter,expiry,admitted_sequence FROM server_membership_invitations WHERE handle=?")
                    .bind(&handle).fetch_one(&mut *conn).await?;
            let dec = Declaration::from_record(&membership, &declaration)?;
            ensure!(
                handle == dec.handle()
                    && inviter == dec.inviter()
                    && expiry >= 0
                    && expiry as u64 == dec.expiry()
                    && admitted == seq,
                "error membership-storage"
            );
            (declaration, request)
        } else {
            (vec![], vec![])
        };
        membership = membership.append(&declaration, &request, &record)?;
        evidence.transitions.push(EvidenceRecord {
            declaration,
            request,
            record,
        });
    }
    let (seq, head): (i64, Vec<u8>) = sqlx::query_as(
        "SELECT sequence,commitment FROM server_e2ee_membership_head WHERE singleton=1",
    )
    .fetch_one(&mut *conn)
    .await?;
    ensure!(
        seq == membership.sequence() as i64 && head == membership.head(),
        "error membership-head-invalid"
    );
    Ok(Current {
        membership,
        evidence,
    })
}
/// Unix seconds that enrollment expiry compares against.
pub fn now() -> Result<i64> {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .ok()
        .and_then(|d| i64::try_from(d.as_secs()).ok())
        .context("error enrollment-clock")
}
async fn observe(conn: &mut SqliteConnection, time: i64) -> Result<i64> {
    ensure!(time >= 0, "error enrollment-clock");
    sqlx::query("INSERT INTO server_membership_clock(singleton,high_water) VALUES(1,?) ON CONFLICT(singleton) DO UPDATE SET high_water=max(high_water,excluded.high_water)")
        .bind(time).execute(&mut *conn).await?;
    let high: i64 =
        sqlx::query_scalar("SELECT high_water FROM server_membership_clock WHERE singleton=1")
            .fetch_one(&mut *conn)
            .await?;
    sqlx::query("UPDATE server_membership_invitations SET expired=1 WHERE admitted_sequence IS NULL AND expiry<=?")
        .bind(high).execute(&mut *conn).await?;
    Ok(high)
}
pub use aven_protocol::claim::membership::ManagementPreparation;
pub use aven_protocol::wire::enrollment::CancelStatus;
async fn append_transition(
    conn: &mut SqliteConnection,
    before: &Membership,
    next: &Membership,
    handle: Option<Hash>,
    record: &[u8],
) -> Result<()> {
    sqlx::query("INSERT INTO server_membership_transitions(sequence,handle,record) VALUES(?,?,?)")
        .bind(next.sequence() as i64)
        .bind(handle.map(|h| h.to_vec()))
        .bind(record)
        .execute(&mut *conn)
        .await?;
    let changed = sqlx::query("UPDATE server_e2ee_membership_head SET sequence=?,commitment=? WHERE singleton=1 AND sequence=? AND commitment=?")
        .bind(next.sequence() as i64).bind(next.head().as_slice()).bind(before.sequence() as i64).bind(before.head().as_slice()).execute(conn).await?;
    ensure!(
        changed.rows_affected() == 1,
        "error membership-head-invalid"
    );
    Ok(())
}
pub(crate) async fn allocator(conn: &mut SqliteConnection, m: &Membership) -> Result<u64> {
    let b = m.publication().binding();
    let (n, high): (i64, i64) = sqlx::query_as(
        "SELECT prefix_count,high_water FROM server_e2ee_allocator WHERE singleton=1 AND stream=?",
    )
    .bind(b.stream_id.as_slice())
    .fetch_one(conn)
    .await?;
    ensure!(
        n >= 0
            && n as u64 == b.prefix_count
            && high >= n
            && high as u64 >= m.current_generation().starts_after,
        "error membership-allocator"
    );
    Ok(high as u64)
}
impl Database {
    pub async fn prepare_membership_management(
        &self,
        auth: &Authentication<'_>,
    ) -> Result<ManagementPreparation> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, false)?;
        let high_water = allocator(&mut tx, &c.membership).await?;
        tx.commit().await?;
        Ok(ManagementPreparation {
            evidence: c.evidence.clone(),
            high_water,
        })
    }
    pub async fn apply_membership_management(
        &self,
        auth: &Authentication<'_>,
        record: &[u8],
    ) -> Result<Vec<u8>> {
        ensure!(record.len() <= MAX_RECORD_BYTES, "error membership-limit");
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, false)?;
        let (signer, action) = Membership::record_action(record)?;
        ensure!(matches!(action, 4 | 5), "error membership-action");
        if !c.evidence.transitions.iter().any(|t| t.record == record) {
            ensure!(signer == auth.device, super::Unauthorized);
            let next = c.membership.append(&[], &[], record)?;
            let high = allocator(&mut tx, &c.membership).await?;
            ensure!(
                action != 5 || next.current_generation().starts_after == high,
                "error membership-cutoff"
            );
            append_transition(&mut tx, &c.membership, &next, None, record).await?;
            for device in c.membership.devices() {
                if !next.has_device(device) {
                    sqlx::query("UPDATE server_membership_invitations SET revoked=1,expired=1 WHERE inviter=? AND admitted_sequence IS NULL")
                        .bind(device.as_slice()).execute(&mut *tx).await?;
                    sqlx::query("DELETE FROM server_e2ee_image_tickets WHERE device=?")
                        .bind(device.as_slice())
                        .execute(&mut *tx)
                        .await?;
                }
            }
        }
        tx.commit().await?;
        Ok(record.to_vec())
    }
}
impl Database {
    /// Replays the complete stored membership history and fills the cache.
    /// Servers run this before accepting requests; storage without published
    /// membership has nothing to verify.
    pub async fn verify_membership_history(&self) -> Result<()> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let published: bool =
            sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_membership_head)")
                .fetch_one(&mut *tx)
                .await?;
        if published {
            self.membership_cache.set(Arc::new(replay(&mut tx).await?));
            current(self, &mut tx).await?;
        }
        tx.commit().await?;
        Ok(())
    }
    /// This read alone accepts a verified ancestor head, after current credential authentication.
    pub async fn membership_evidence(&self, auth: &Authentication<'_>) -> Result<Evidence> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, true)?;
        tx.commit().await?;
        Ok(c.evidence.clone())
    }
    pub async fn register_membership_invitation_at(
        &self,
        auth: &Authentication<'_>,
        raw: &[u8],
        time: i64,
    ) -> Result<RegistrationStatus> {
        ensure!(raw.len() == DECLARATION_BYTES, "error membership-limit");
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, false)?;
        let d = Declaration::from_record(&c.membership, raw)?;
        ensure!(d.inviter() == auth.device, super::Unauthorized);
        let expiry = i64::try_from(d.expiry()).context("error enrollment-expired")?;
        let high = observe(&mut tx, time).await?;
        let stored: Option<Vec<u8>> = sqlx::query_scalar(
            "SELECT declaration FROM server_membership_invitations WHERE handle=?",
        )
        .bind(d.handle().as_slice())
        .fetch_optional(&mut *tx)
        .await?;
        if let Some(stored) = stored {
            ensure!(stored == raw, "error enrollment-conflict");
        } else {
            let count: i64 =
                sqlx::query_scalar("SELECT count(*) FROM server_membership_invitations")
                    .fetch_one(&mut *tx)
                    .await?;
            ensure!(
                count < MAX_INVITATIONS as i64 && c.membership.device_count() < MAX_DEVICES,
                "error membership-limit"
            );
            ensure!(
                expiry <= high || expiry - high <= 3600,
                "error enrollment-expired"
            );
            let unfinished: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_membership_invitations WHERE inviter=? AND expired=0 AND admitted_sequence IS NULL)")
                .bind(d.inviter().as_slice()).fetch_one(&mut *tx).await?;
            if unfinished {
                tx.commit().await?;
                anyhow::bail!("error enrollment-unfinished");
            }
            sqlx::query("INSERT INTO server_membership_invitations(handle,inviter,declaration,expiry,expired) VALUES(?,?,?,?,?)")
                .bind(d.handle().as_slice()).bind(d.inviter().as_slice()).bind(raw).bind(expiry).bind(expiry <= high).execute(&mut *tx).await?;
        }
        let (expired, consumed): (bool, bool) = sqlx::query_as("SELECT expired,admitted_sequence IS NOT NULL FROM server_membership_invitations WHERE handle=?")
            .bind(d.handle().as_slice()).fetch_one(&mut *tx).await?;
        tx.commit().await?;
        Ok(if consumed {
            RegistrationStatus::Consumed
        } else if expired {
            RegistrationStatus::Expired
        } else {
            RegistrationStatus::Open
        })
    }
    pub async fn post_membership_request_at(
        &self,
        vault: Hash,
        handle: Hash,
        request: &[u8],
        time: i64,
    ) -> Result<()> {
        request_parts(request, &handle)?;
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        check(vault == c.membership.genesis().context().vault_id)?;
        observe(&mut tx, time).await?;
        let (stored, expired, revoked, admitted): (Option<Vec<u8>>, bool, bool, bool) = sqlx::query_as(
            "SELECT request,expired,revoked,admitted_sequence IS NOT NULL FROM server_membership_invitations WHERE handle=?",
        )
        .bind(handle.as_slice())
        .fetch_optional(&mut *tx)
        .await?
        .context("error enrollment-unavailable")?;
        ensure!(
            !revoked && (!admitted || c.membership.has_admission(handle)),
            "error enrollment-revoked"
        );
        if let Some(stored) = stored {
            ensure!(stored == request, "error enrollment-request-occupied");
        } else {
            if expired {
                tx.commit().await?;
                anyhow::bail!("error enrollment-expired");
            }
            sqlx::query("UPDATE server_membership_invitations SET request=? WHERE handle=?")
                .bind(request)
                .bind(handle.as_slice())
                .execute(&mut *tx)
                .await?;
        }
        tx.commit().await?;
        Ok(())
    }
    pub async fn membership_mailbox(&self, vault: Hash, handle: Hash) -> Result<Mailbox> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        check(vault == c.membership.genesis().context().vault_id)?;
        let (declaration, request, admission): (Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>) = sqlx::query_as("SELECT i.declaration,i.request,a.record FROM server_membership_invitations i LEFT JOIN server_membership_transitions a ON a.handle=i.handle WHERE i.handle=?")
            .bind(handle.as_slice()).fetch_optional(&mut *tx).await?.context("error enrollment-unavailable")?;
        if admission.is_some() {
            ensure!(
                c.membership.has_admission(handle),
                "error enrollment-revoked"
            );
        } else {
            let d = Declaration::from_record(&c.membership, &declaration)?;
            check(d.handle() == handle)?;
        }
        tx.commit().await?;
        Ok(Mailbox {
            declaration,
            request,
            admission,
        })
    }
    /// Terminally fences a registered, unadmitted invitation of the caller so
    /// no new admission can commit. Serialized with admission; idempotent.
    /// Unknown handles allocate nothing.
    pub async fn cancel_membership_invitation_at(
        &self,
        auth: &Authentication<'_>,
        handle: Hash,
        time: i64,
    ) -> Result<CancelStatus> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, false)?;
        let (declaration, inviter, admitted): (Vec<u8>, Vec<u8>, bool) = sqlx::query_as("SELECT declaration,inviter,admitted_sequence IS NOT NULL FROM server_membership_invitations WHERE handle=?")
            .bind(handle.as_slice())
            .fetch_optional(&mut *tx)
            .await?
            .context("error enrollment-unavailable")?;
        // The immutable stored row binds the exact declaration and its inviter.
        ensure!(
            inviter == auth.device
                && Declaration::from_record(&c.membership, &declaration)?.handle() == handle,
            super::Unauthorized
        );
        let status = if admitted {
            CancelStatus::Admitted
        } else {
            observe(&mut tx, time).await?;
            sqlx::query("UPDATE server_membership_invitations SET expired=1 WHERE handle=?")
                .bind(handle.as_slice())
                .execute(&mut *tx)
                .await?;
            CancelStatus::Cancelled
        };
        tx.commit().await?;
        Ok(status)
    }
    pub async fn admit_membership_device_at(
        &self,
        auth: &Authentication<'_>,
        handle: Hash,
        record: &[u8],
        time: i64,
    ) -> Result<Vec<u8>> {
        ensure!(record.len() <= MAX_RECORD_BYTES, "error membership-limit");
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let c = current(self, &mut tx).await?;
        c.membership.authenticate(auth, false)?;
        type Row = (Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>, bool);
        let (declaration, request, saved, fenced): Row = sqlx::query_as("SELECT i.declaration,i.request,a.record,i.expired FROM server_membership_invitations i LEFT JOIN server_membership_transitions a ON a.handle=i.handle WHERE i.handle=?")
            .bind(handle.as_slice()).fetch_optional(&mut *tx).await?.context("error enrollment-unavailable")?;
        if let Some(saved) = saved {
            ensure!(saved == record, "error enrollment-conflict");
            observe(&mut tx, time).await?;
        } else {
            let d = Declaration::from_record(&c.membership, &declaration)?;
            ensure!(
                d.handle() == handle && d.inviter() == auth.device,
                super::Unauthorized
            );
            let high = observe(&mut tx, time).await?;
            // Cancelled and clock-expired rows stay terminal for new admission.
            if fenced || high as u64 >= d.expiry() {
                tx.commit().await?;
                anyhow::bail!("error enrollment-expired");
            }
            let request = request.context("error enrollment-request-missing")?;
            let next = c.membership.append(&declaration, &request, record)?;
            append_transition(&mut tx, &c.membership, &next, Some(handle), record).await?;
            sqlx::query(
                "UPDATE server_membership_invitations SET admitted_sequence=? WHERE handle=?",
            )
            .bind(next.sequence() as i64)
            .bind(handle.as_slice())
            .execute(&mut *tx)
            .await?;
        }
        tx.commit().await?;
        Ok(record.to_vec())
    }
}
#[cfg(test)]
mod tests;

impl Database {
    pub async fn membership_checkpoint_mirror(&self) -> Result<Option<(Hash, u64, Hash, Hash)>> {
        let mut conn = self.acquire_reader().await?;
        type Row = (Vec<u8>, i64, Vec<u8>, Vec<u8>);
        let row: Option<Row> = sqlx::query_as("SELECT identity,sequence,head,evidence FROM local_membership_checkpoint WHERE singleton=1").fetch_optional(&mut *conn).await?;
        row.map(|(id, seq, head, evidence)| {
            Ok((
                id.try_into()
                    .map_err(|_| anyhow::anyhow!("error membership-mirror"))?,
                u64::try_from(seq)?,
                head.try_into()
                    .map_err(|_| anyhow::anyhow!("error membership-mirror"))?,
                evidence
                    .try_into()
                    .map_err(|_| anyhow::anyhow!("error membership-mirror"))?,
            ))
        })
        .transpose()
    }
    pub async fn mirror_membership_checkpoint(
        &self,
        identity: Hash,
        m: &Membership,
        evidence: Hash,
    ) -> Result<()> {
        let mut conn = self.acquire_writer().await?;
        let mut tx = begin_immediate(&mut conn).await?;
        let valid: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM local_peer_enrollment WHERE identity=? AND client_id=(SELECT value FROM meta WHERE key='client_id'))").bind(identity.as_slice()).fetch_one(&mut *tx).await?;
        ensure!(valid, "error membership-identity");
        type MirrorRow = (Vec<u8>, i64, Vec<u8>, Vec<u8>);
        let old: Option<MirrorRow> = sqlx::query_as("SELECT identity,sequence,head,evidence FROM local_membership_checkpoint WHERE singleton=1").fetch_optional(&mut *tx).await?;
        if let Some((id, seq, head, digest)) = old {
            ensure!(
                id == identity
                    && m.head_at(u64::try_from(seq)?).is_some_and(|h| head == h)
                    && (seq != m.sequence() as i64 || digest == evidence),
                "error membership-mirror"
            );
        }
        sqlx::query("INSERT INTO local_membership_checkpoint(singleton,identity,sequence,head,evidence) VALUES(1,?,?,?,?) ON CONFLICT(singleton) DO UPDATE SET sequence=excluded.sequence,head=excluded.head,evidence=excluded.evidence")
            .bind(identity.as_slice()).bind(m.sequence() as i64).bind(m.head().as_slice()).bind(evidence.as_slice()).execute(&mut *tx).await?;
        tx.commit().await?;
        Ok(())
    }
}