The Files tab dropzone still enforced the old 20 MiB cap client- and
server-side while every other path had moved to 2 GiB. Also corrects the
stale '100 MiB' message on the video upload rejection.
- group-files: origin filter facet; GET /files/:id returns 410 for unavailable
rows after the ACL recheck; fix latent group_members.status filter and unbound
workspace parameter that would 500 listFiles
- archive-bot: markRedacted revokes live group_file_shares rows in-transaction
- new live-DB integration suite for the archive index surface
Wires the three parallel group-cloud tracks to one surface and fixes the
seams between them:
- chat_media_archive.room_id becomes nullable (direct web uploads have no
Matrix room yet — the contract's null-event pattern extends to the room);
core GroupFile.roomId and ArchiveEntryInput.roomId follow.
- mime_class gains 'archive' (zip) across the 029 CHECK, validation
MIME_CLASSES and the bot classifier — one vocabulary everywhere.
- registerArchiveEntry claim keys are null-safe for room-less uploads.
- New: POST /files/groups/:groupId/upload — FileInterceptor (20 MiB) streams
through magic-byte sniffing, library-safe naming, oCIS upload and the
metadata index, behind group-membership ACL (Owner/Admin bypass).
- safeFileName/sniffMime promoted to packages/core (file-names.ts,
file-mime.ts) so the bot and the api share one implementation; the bot's
local modules become re-export shims; @types/node added to core.
Verified: api typecheck clean, group-files 22/22, bot 74/74, web typecheck
clean; 029 re-applied clean on a fresh scratch DB (room_id nullable,
archive class, claim_key unique all confirmed in the catalog).