feat(meditate): the library's accesses name nobody, and the privacy page says so

An access to the library's files now holds from the hour begun, so
everyone given one within the same hour gets the same and a fetch can't
be told apart by account. The privacy page says what the library means
for people: signed in alone, fetched naming no account, what they
listened to sealed. ADR 0038, D16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 16:47:23 +01:00
commit 0b6f27d278
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 44 additions and 2 deletions

View file

@ -35,9 +35,15 @@ final class LibraryFileAccess {
return [for (var i = 0; i < 32; i++) random.nextInt(256)];
}
/// An access from [now] on, and until when it holds.
/// An access from [now] on, and until when it holds: from the hour begun,
/// so it is the same for everyone given it within that hour, and names
/// nobody (the privacy page's point).
({String token, DateTime until}) issue(DateTime now) {
final until = now.add(lifetime);
const hour = Duration.millisecondsPerHour;
final until = DateTime.fromMillisecondsSinceEpoch(
now.millisecondsSinceEpoch ~/ hour * hour,
isUtc: true,
).add(lifetime);
final seconds = until.millisecondsSinceEpoch ~/ 1000;
return (token: '$seconds.${_sign('$seconds')}', until: until);
}

View file

@ -54,6 +54,25 @@ const List<PrivacyPagePoint> _privacyPagePoints = [
'was sie enthalten. Ein Gerät, das nicht verschlüsseln kann, bewahrt '
'keine auf.',
),
(
english:
'Where this instance offers a library of recordings, only people '
'signed in get it. Fetching a recording names no account: your '
'device shows an access the instance signed alike for everyone it '
'gave the library to within the same hour, so beyond the address a '
'request comes from, the instance can’t tell who listens to what. '
'What you listened to, your favourites and the lessons you did are '
'sealed with your key as your sittings are.',
german:
'Wo diese Instanz eine Bibliothek mit Aufnahmen anbietet, bekommen '
'sie nur angemeldete Personen. Eine Aufnahme zu laden nennt kein '
'Konto: Dein Gerät zeigt eine Zugangsberechtigung, die die Instanz '
'allen gleich signiert, denen sie die Bibliothek in derselben Stunde '
'gab. Über die Adresse hinaus, von der eine Anfrage kommt, weiß die '
'Instanz also nicht, wer was hört. Was du gehört hast, deine '
'Favoriten und die Lektionen, die du gemacht hast, werden wie deine '
'Sitzungen mit deinem Schlüssel verschlüsselt.',
),
(
english:
'A dream you speak is written by your device’s own speech '

View file

@ -54,6 +54,10 @@ void main() {
expect(english, contains('Google'));
expect(german, contains('Spracherkennung'));
expect(german, contains('Google'));
// The library (D16): for people signed in, fetched naming nobody.
expect(english, contains('library'));
expect(english, contains('names no account'));
expect(german, contains('Bibliothek'));
});
test('its basics name the app it serves, `meditate`, so only Meditate '

View file

@ -119,6 +119,19 @@ items:
await expired.drain<void>();
});
test('an access names nobody: everyone given one within the same hour '
'gets the same', () {
final hour = DateTime.utc(2026, 11, 3, 14);
final early = access.issue(hour.add(const Duration(minutes: 1)));
final late = access.issue(hour.add(const Duration(minutes: 59)));
expect(late.token, early.token);
expect(early.until, hour.add(LibraryFileAccess.lifetime));
expect(
access.issue(hour.add(const Duration(minutes: 61))).token,
isNot(early.token),
);
});
test('serves only the files the manifest names, by their served names: '
'a path of the folder, or one out of it, is no such name', () async {
for (final path in [