fix(privacy): what the hostile instance found (ADR 0019)

Each was found by the hostile-instance test (next commit) and has a test
of its own:
- a write decides on its key from the records and the tree read anew: a
  studyset missing from a tree read before, or moved since, looked
  readable;
- each studyset on the way up decides on its own first: its own record,
  private or made readable (a readable studyset the instance moved under a
  private one was taken as private, so a private card moved in and was
  opened with the other tree), or the key its own row was seen sealed
  with, learned from that row before a write (a topic the instance moved
  to the top level was written readable on a device that never read it);
  one sealed under a key not here refuses the write (a device that never
  saw a studyset private, its key record withheld, wrote into it
  readable);
- settings keep the newest versions seen: a row older than any of them,
  or than another read with it, isn't read, so withholding a newer row and
  handing back an older one rolls nothing back; Count me folds off where
  any row says off;
- the upkeep reads only readable tags and settings, and a PDF read by its
  id never refuses as a write would;
- two queries passed nullable ids to `id IN`, which Serverpod refuses at
  run time: a path's step revisions and answers couldn't be read.

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-22 12:40:12 +02:00
commit f658cb13da
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -291,6 +291,17 @@ void main() {
);
});
test("Count me is off as two devices' settings fold where either says "
'off: an instance handing back an older on never switches it on (ADR '
'0019, the final reading of 22 Oct)', () {
const on = PersonSettings(countLearning: true);
const off = PersonSettings();
expect(on.foldedWith(off).countLearning, isFalse);
expect(off.foldedWith(on).countLearning, isFalse);
expect(on.foldedWith(on).countLearning, isTrue);
});
test('hiding the tags from Jellyfin while studying survives a round '
'trip, in the ninth schema, left out while off; on, on either '
'device, it stays on as they fold', () {