fix(train): the person's key read before one is chosen to write with

TrainKeys.keyToWrite refused with KeysMissing where nothing had read the
key rows yet, the account's keys on the device all the same, and would
have made a second key beside one already there had it unlocked. It
reads them first now whenever no key is chosen yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 14:00:24 +01:00
commit c7acec3a05
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 17 additions and 1 deletions

View file

@ -61,8 +61,13 @@ class TrainKeys(
_unlocked && _madeAt(keyId) != null && cipher.has(keyId);
/// The key to seal with, made and kept the first time; throws
/// [KeysMissing] without the account's keys.
/// [KeysMissing] without the account's keys. The key rows are read first
/// where none was chosen yet, so that no second key is made beside one
/// already there.
Future<String> keyToWrite() async {
if (_writeKey == null) {
await refresh();
}
if (_writeKey case final key?) {
return key;
}

View file

@ -52,6 +52,17 @@ void main() {
expect(rows.rows, hasLength(1));
});
test('a device that has read no key rows yet writes with the key already '
'there, and makes none beside it', () async {
final phone = await device(alice);
final keyId = await phone.keyToWrite();
final laptop = TrainKeys(rows, SodiumContentCipher(), () async => alice);
opened.add(laptop);
expect(await laptop.keyToWrite(), keyId);
expect(rows.rows, hasLength(1));
});
test('two devices that made a key each, offline, both write with the '
'older one once they see both', () async {
final phone = await device(alice);