fix(feedback): a suggestion takes along only its own tree's sealed files (ADR 0019)

The card's, note's or step's own id goes with a suggestion, and a sealed
file goes along readable only where it is under the key of that item's
tree: a file of another private tree, named by a row the instance
planted, is left out and never uploaded readable (the short reading of
22 Oct, finding 12).

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 10:29:45 +02:00
commit 5a61f2ea07
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -129,6 +129,72 @@ void main() {
]);
});
test("a suggestion takes along readable only files of the item's own "
'tree: one of another private tree, named by a row the instance '
'planted, is never uploaded', () async {
final rows = FakeAssetGateway();
final cache = MemoryAssetFileCache();
final sealedAssets = AssetRepository(
rows,
FakeAssetServerGateway(),
cache,
FakeAssetSealing(
keys: {'own-tree': 'k-own', 'other-tree': 'k-other'},
itemKeys: {'card-1': 'k-own'},
),
);
final sealedFeedback = FeedbackRepository(
written,
server,
cards,
notes,
LearningPathRepository(FakeLearningPathGateway()),
sealedAssets,
);
final mine = await sealedAssets.add(
image,
fileName: 'cat.png',
mimeType: 'image/png',
studysetId: 'own-tree',
);
final secret = utf8.encode('a secret picture');
final theirs = await sealedAssets.add(
secret,
fileName: 'secret.png',
mimeType: 'image/png',
studysetId: 'other-tree',
);
Map<String, Object?> imageBlock(String id) => {
'type': 'image',
'data': {'url': 'asset:$id'},
};
await sealedFeedback.suggestCard(
CardContent(
front: RichContent({
'document': {
'type': 'page',
'children': [imageBlock(mine), imageBlock(theirs)],
},
}),
back: RichContent.plain('a cat'),
published: origin,
),
body: 'Pictures help.',
itemId: 'card-1',
);
final files = written.threads.single.json['files']! as List<Object?>;
expect([for (final file in files) (file! as Map)['assetId']], [mine]);
// A readable copy of the own file waits to upload; the other's never.
final readable = {
for (final row in rows.rows)
if (row.keyId == null) row.sha256,
};
expect(readable, {imageHash});
expect(readable, isNot(contains(sha256Of(secret))));
});
test("an accepted suggestion becomes the author's own card, its files "
'theirs, and is recorded so', () async {
final id = await cards.create(