feat(privacy): a private studyset's files are sealed with its key (ADR 0019, D5)
- `AssetRepository` seals a file of a private studyset with its tree's key for its asset row before hashing it (`AssetSealing`, which `Privacy` does): the row's hash and size and the file uploaded are the sealed file's, the upload says it is encrypted, and the row's payload, sealed too, carries the file name and the hash of what was sealed (asset schema 2). A download is checked against the row's hash, opened for its row and checked against what was sealed. This device keeps the sealed file to upload and the opened one, by its own hash, for what reads files by path. - The same file is a row per key: in a private and a readable studyset, or two private trees, it is two. - Every place adding a file names its studyset: the editors' pictures and sounds (`StudysetFiles`, which every screen of a studyset and the PDF reader set), PDFs and screenshots, Anki imports, copies and their updates (a copy's top studyset is made first, so its files take its key), and accepted suggestions (by their item's tree). - Making a tree private or not, or moving a topic into another tree, moves its files: each under another key is added anew under the tree's, the content names the new row (whole `asset:` values and a PDF's file, never text), and the old row is blanked and deleted once nothing else names it. - Tests: a private studyset's file never uploads readable; a sealed file moved to another row, or under a key not open here, doesn't open; a changed file is refused by its hash; one row per key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
dcdc9c1542
commit
e368bba2dd
1 changed files with 1402 additions and 228 deletions
|
|
@ -16,6 +16,7 @@ import 'package:halkyon_learn_flutter/src/feature/note/data/note_repository.dart
|
|||
import 'package:halkyon_learn_flutter/src/feature/note/model/note_content.dart';
|
||||
|
||||
import '../../support/fake_asset_gateway.dart';
|
||||
import '../../support/fake_asset_sealing.dart';
|
||||
import '../../support/fake_asset_server_gateway.dart';
|
||||
import '../../support/fake_card_gateway.dart';
|
||||
import '../../support/fake_feedback_gateways.dart';
|
||||
|
|
@ -49,6 +50,7 @@ void main() {
|
|||
FakeAssetGateway(),
|
||||
FakeAssetServerGateway(),
|
||||
MemoryAssetFileCache(),
|
||||
FakeAssetSealing(),
|
||||
);
|
||||
feedback = FeedbackRepository(
|
||||
written,
|
||||
|
|
@ -86,6 +88,7 @@ void main() {
|
|||
image,
|
||||
fileName: 'cat.png',
|
||||
mimeType: 'image/png',
|
||||
studysetId: null,
|
||||
);
|
||||
|
||||
await feedback.suggestCard(
|
||||
|
|
@ -176,7 +179,10 @@ void main() {
|
|||
expect(card.content.back.plainText, 'a cat');
|
||||
final front = card.content.front.plainText;
|
||||
expect(front, isNot(contains(learnersAsset)));
|
||||
expect(await assets.idOf(imageHash), front.substring('asset:'.length));
|
||||
expect(
|
||||
await assets.idOf(imageHash, studysetId: null),
|
||||
front.substring('asset:'.length),
|
||||
);
|
||||
expect(card.content.report?.note, 'Mine.');
|
||||
expect(written.decisions.single, (
|
||||
threadId: 'thread-1',
|
||||
|
|
|
|||
Loading…
Reference in a new issue