Skip to main content

max / goingson

Date the confirmations list, not the group list The confirmations section is the security-bearing step of the invite flow: an admin compares a fingerprint and admits somebody. It said "the group directory was last refreshed", because that was the only number synckit exposed and the comment there said so. synckit-client 0.9.1 adds pending_confirmations_refreshed_at, the freshness of exactly the rows pending_confirmations returns, reporting the stalest group it drew from. The section states that instead, and the paragraph explaining why it could not comes out.
Author: Max Johnson <me@maxj.phd> · 2026-08-24 22:32 UTC
Signed with PGP, not checked
Commit: 620e00db898c105c417c8656b6410f6196cc0f7b
Parent: 694ca02
3 files changed, +66 insertions, -42 deletions
M Cargo.lock +18 -18
@@ -1387,7 +1387,7 @@
1387 1387 "libc",
1388 1388 "option-ext",
1389 1389 "redox_users",
1390 - "windows-sys 0.59.0",
1390 + "windows-sys 0.61.2",
1391 1391 ]
1392 1392
1393 1393 [[package]]
@@ -1628,7 +1628,7 @@
1628 1628 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1629 1629 dependencies = [
1630 1630 "libc",
1631 - "windows-sys 0.59.0",
1631 + "windows-sys 0.61.2",
1632 1632 ]
1633 1633
1634 1634 [[package]]
@@ -3639,7 +3639,7 @@
3639 3639 "png 0.18.1",
3640 3640 "serde",
3641 3641 "thiserror 2.0.20",
3642 - "windows-sys 0.60.2",
3642 + "windows-sys 0.61.2",
3643 3643 ]
3644 3644
3645 3645 [[package]]
@@ -3772,7 +3772,7 @@
3772 3772 source = "registry+https://github.com/rust-lang/crates.io-index"
3773 3773 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
3774 3774 dependencies = [
3775 - "windows-sys 0.59.0",
3775 + "windows-sys 0.61.2",
3776 3776 ]
3777 3777
3778 3778 [[package]]
@@ -5195,7 +5195,7 @@
5195 5195 "errno",
5196 5196 "libc",
5197 5197 "linux-raw-sys",
5198 - "windows-sys 0.59.0",
5198 + "windows-sys 0.61.2",
5199 5199 ]
5200 5200
5201 5201 [[package]]
@@ -5252,7 +5252,7 @@
5252 5252 "security-framework",
5253 5253 "security-framework-sys",
5254 5254 "webpki-root-certs",
5255 - "windows-sys 0.59.0",
5255 + "windows-sys 0.61.2",
5256 5256 ]
5257 5257
5258 5258 [[package]]
@@ -5792,7 +5792,7 @@
5792 5792 checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
5793 5793 dependencies = [
5794 5794 "libc",
5795 - "windows-sys 0.60.2",
5795 + "windows-sys 0.61.2",
5796 5796 ]
5797 5797
5798 5798 [[package]]
@@ -5991,7 +5991,7 @@
5991 5991
5992 5992 [[package]]
5993 5993 name = "synckit-client"
5994 - version = "0.9.0"
5994 + version = "0.9.1"
5995 5995 dependencies = [
5996 5996 "apple-native-keyring-store",
5997 5997 "argon2",
@@ -6564,10 +6564,10 @@
6564 6564 checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
6565 6565 dependencies = [
6566 6566 "fastrand",
6567 - "getrandom 0.3.4",
6567 + "getrandom 0.4.3",
6568 6568 "once_cell",
6569 6569 "rustix",
6570 - "windows-sys 0.59.0",
6570 + "windows-sys 0.61.2",
6571 6571 ]
6572 6572
6573 6573 [[package]]
@@ -6589,7 +6589,7 @@
6589 6589 "parking_lot",
6590 6590 "rustix",
6591 6591 "signal-hook",
6592 - "windows-sys 0.60.2",
6592 + "windows-sys 0.61.2",
6593 6593 ]
6594 6594
6595 6595 [[package]]
@@ -7071,7 +7071,7 @@
7071 7071 "png 0.18.1",
7072 7072 "serde",
7073 7073 "thiserror 2.0.20",
7074 - "windows-sys 0.60.2",
7074 + "windows-sys 0.61.2",
7075 7075 ]
7076 7076
7077 7077 [[package]]
@@ -7106,7 +7106,7 @@
7106 7106 dependencies = [
7107 7107 "memoffset",
7108 7108 "tempfile",
7109 - "windows-sys 0.60.2",
7109 + "windows-sys 0.61.2",
7110 7110 ]
7111 7111
7112 7112 [[package]]
@@ -7636,7 +7636,7 @@
7636 7636 source = "registry+https://github.com/rust-lang/crates.io-index"
7637 7637 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
7638 7638 dependencies = [
7639 - "windows-sys 0.59.0",
7639 + "windows-sys 0.61.2",
7640 7640 ]
7641 7641
7642 7642 [[package]]
@@ -8493,10 +8493,6 @@
8493 8493 "winnow 1.0.4",
8494 8494 ]
8495 8495
8496 - [[patch.unused]]
8497 - name = "ops-status"
8498 - version = "0.1.0"
8499 -
8500 8496 [[patch.unused]]
8501 8497 name = "quasi-axum"
8502 8498 version = "0.56.0"
@@ -8512,3 +8508,7 @@
8512 8508 [[patch.unused]]
8513 8509 name = "quasi-store"
8514 8510 version = "0.1.0"
8511 +
8512 + [[patch.unused]]
8513 + name = "ops-status"
8514 + version = "0.1.0"
@@ -285,8 +285,8 @@
285 285 let conn = conn(app)?;
286 286 let waiting = directory::pending_confirmations(&conn)
287 287 .map_err(|error| RouteError::internal(error.to_string()))?;
288 - let refreshed =
289 - directory::refreshed_at(&conn).map_err(|error| RouteError::internal(error.to_string()))?;
288 + let refreshed = directory::pending_confirmations_refreshed_at(&conn)
289 + .map_err(|error| RouteError::internal(error.to_string()))?;
290 290 drop(conn);
291 291
292 292 if waiting.is_empty() {
@@ -339,21 +339,17 @@
339 339 Node::list(rows),
340 340 ];
341 341
342 - // Stated, and stated as the number it actually is. `directory::refreshed_at`
343 - // is `MIN(refreshed_at)` over `sync_groups`, so it is the group list's
344 - // freshness; `sync_invitations` carries its own `refreshed_at` and synckit
345 - // exposes no reader for it (filed on synckit-client). The two move together
346 - // on a good cycle, because invitations are fetched on the same pass, and
347 - // they come apart on a bad one: `write_invitations` is scoped per group
348 - // precisely so one group's failed fetch does not blank the others.
349 - //
350 - // So the wording says which list the timestamp belongs to rather than
351 - // implying it covers this one. A section whose whole job is a security
352 - // decision should not overstate how current it is.
342 + // The age of this list, not of a neighbouring one.
343 + // `pending_confirmations_refreshed_at` is the freshness of exactly the rows
344 + // `pending_confirmations` returned, and it reports the stalest group it drew
345 + // from: `write_invitations` is scoped per group so one group's failed fetch
346 + // does not blank the others, which is also the cycle where a number about
347 + // the group list would overstate this one. A section whose whole job is a
348 + // security decision should not do that.
353 349 if let Some(at) = refreshed {
354 350 nodes.push(Node::text(format!(
355 - "The group directory was last refreshed {at}. Invitations arrive on the same \
356 - cycle, so somebody who accepted since then is not here yet."
351 + "This list was last refreshed {at}. Somebody who accepted since then is not \
352 + here yet."
357 353 )));
358 354 }
359 355
@@ -1082,12 +1082,11 @@
1082 1082 );
1083 1083 }
1084 1084
1085 - /// The only staleness reader synckit exposes is `MIN(refreshed_at)` over
1086 - /// `sync_groups`, so the section can state the group list's freshness and not
1087 - /// the invitation list's. It says which one it is rather than implying the
1088 - /// stronger claim. Filed on synckit-client as `0e8af5b2`.
1085 + /// The section dates the list it is drawing. `pending_confirmations_refreshed_at`
1086 + /// is the freshness of exactly the rows above it, so the sentence is about them
1087 + /// and not about the group list, which refreshes on a different schedule.
1089 1088 #[tokio::test]
1090 - async fn the_confirmations_section_says_which_list_its_timestamp_is_about() {
1089 + async fn the_confirmations_section_dates_the_list_it_is_drawing() {
1091 1090 let state = state().await;
1092 1091 known_group(&state, 1, "The Firm", true);
1093 1092 known_invitation(
@@ -1101,12 +1100,41 @@
1101 1100 );
1102 1101
1103 1102 let pane = sharing(&state);
1103 + assert!(pane.contains("This list was last refreshed"), "{pane}");
1104 1104 assert!(
1105 - pane.contains("The group directory was last refreshed"),
1106 - "{pane}"
1105 + !pane.contains("The group directory was last refreshed"),
1106 + "the group list is a different list on a different schedule: {pane}"
1107 1107 );
1108 + }
1109 +
1110 + /// The case the per-list reader exists for: the group list refreshed on a cycle
1111 + /// where this group's invitation fetch failed. The section must report the older
1112 + /// number, because that is how old what it is drawing actually is.
1113 + #[tokio::test]
1114 + async fn the_confirmations_timestamp_is_the_invitation_lists_not_the_groups() {
1115 + let state = state().await;
1116 + known_group(&state, 1, "The Firm", true);
1117 + known_invitation(
1118 + &state,
1119 + 1,
1120 + 11,
1121 + synckit_client::InvitationState::Accepted,
1122 + Some("aa:bb:cc:dd"),
1123 + None,
1124 + &far_future(),
1125 + );
1126 + {
1127 + let conn = state.db.conn().unwrap();
1128 + conn.execute(
1129 + "UPDATE sync_invitations SET refreshed_at = '2026-01-01T00:00:00.000Z'",
1130 + [],
1131 + )
1132 + .unwrap();
1133 + }
1134 +
1135 + let pane = sharing(&state);
1108 1136 assert!(
1109 - !pane.contains("Invitations last updated"),
1110 - "no claim about a number nothing reads: {pane}"
1137 + pane.contains("This list was last refreshed 2026-01-01T00:00:00.000Z"),
1138 + "{pane}"
1111 1139 );
1112 1140 }