Repository navigation
Keep the files an MLS message carries (GRYT-1523) - #246
Merged
Merged
Conversation
An MLS message never goes through chat:send, so no message row pointed at the files it carried, and the media sweep deleted them 30 minutes after upload. mls:send now takes attachmentIds. They're checked by chat:send's rules for a sealed DM: attach_files, at most ten, each one a file the sender can already read, and under the upload cap. Then they're recorded against the log entry in a new mls_attachments table. That table counts as a reference for the media sweep, and as somewhere a file was sent, so the other person can fetch it. Its rows go with the log entry, by a cascade. When retention drops an entry, its files are deleted straight away if nothing else holds them, the way a deleted message's are. Otherwise the media sweep takes them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Sep 28, 2026
sivert-io
marked this pull request as ready for review
September 28, 2026 08:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What to look at
mls_attachmentstable insrc/db/sqlite/connection.ts:(file_id, group_id, seq), with a foreign key tomls_logandON DELETE CASCADE. So a ref goes whenever its entry goes, whether retention drops it ordropMlsGroupForConversationdoes. It's the same shapemessage_attachmentshas againstmessages. The table is new, so it's only aCREATE TABLE IF NOT EXISTSwith no migration.getFileOwnershipinmessages.tsnow counts a file as sent to a conversation when an MLS entry there holds it. That's what lets the other person fetch it, and it's the one change here that widens who can read a file.getAllReferencedAttachmentIdsandisFileReferencedByMessageread the new table too.sweepMlsnow also returnsfileIds, the files held by the entries it's about to drop, including a group whose conversation is gone.runMlsRetentionhands them todeleteUnreferencedFiles, which is what deleting a message does. That only deletes what nothing else holds, and it needs S3. Anything it leaves, the media sweep gets on its next run.attachmentRefusalrepeatchat:send's rules for a sealed DM rather than sharing code with it, sincechat:sendanswers some of them with a bare string. The rules areattach_files, ten at most, each file one the sender can already read, and under the upload cap.chat:senddoesn't check one either. A sender can attach any file they can already read, which is how forwarding works in a sealed DM too.What's in it
mls:sendtakes an optionalattachmentIds: string[]. Duplicates are folded. An id that isn't a non-empty string getsinvalid_payload, and so does a proposal that carries any.placeholder: falsemessages can carry files. The count feeds the spam filter'sattachments, which was hard-coded to 0.Tests
In the handler tests: a file Alice uploaded, sent with
placeholder: false, is referenced, readable by Bob, and recorded against that seq. Bob's file, a missing id, eleven ids and a non-list are refused. After the entry ages past retention, the sweep returns the file, the media sweep would take it, and Bob can't read it any more. In the db tests: an aged entry's files come back from the sweep and a newer one's stay, then a group dropped with its conversation hands back the rest. Full suite: 1783 pass.Task: GRYT-1523. Docs: Gryt-chat/docs#143, which merges after this one.
🤖 Generated with Claude Code