T-HUB/docs/occupymedia-storage.md
glass beadian 43035a94e8 Sync storage ownership doc with provisioned R2 reality
The hub-* buckets were provisioned 2026-10-08 (files-blobs, matrix-media,
media-library, pads); the doc's occupymedia-files-blobs name never existed.
hub-files-blobs is now live as the oCIS S3NG bucket (occ-files CT 320,
deployed 2026-10-10) with a bucket-scoped S3 key.
2026-10-11 04:19:28 +00:00

148 lines
9.6 KiB
Markdown

# OccupyMedia shared files, documents and pads — phase one
**Implemented: contracts, config validation and independent compose scaffolding.**
No services, Matrix upgrade, archive bot, index API, database migration or UI have
been deployed/implemented by this phase. Existing API/media-worker storage paths
and deployment edits are preserved. [Deployment steps and resources](../infra/occupymedia/files/README.md)
are the next operator runbook. Design input: `runs/wp4/fileshare-research.md`.
## Storage ownership
| Payload bucket | Exclusive owner | Status |
|---|---|---|
| `occupymedia-matrix-media` | Future Tuwunel native S3 media | Existing bucket; live conduwuit still local |
| `hub-files-blobs` | oCIS S3NG, working files/docs and pad exports | Existing bucket (provisioned 2026-10-08); oCIS deployed 2026-10-10 on occ-files CT 320. S3 key scoped to this bucket only |
| `hub-pads` | Future compliant pad persistence | Existing bucket; unused until a compliant pad adapter exists |
| `hub-media-library` | Reserved — library copies requiring separate attribution | Existing bucket; unused |
| `occupymedia-media-encrypted` | Tactical Media Broadcast Hub media/library, existing envelope format | Existing configuration; preserve KEK |
| `occupymedia-peertube-videos` | PeerTube video objects | Existing dedicated bucket |
| `occupymedia-peertube-playlists` | PeerTube playlists | Existing dedicated bucket |
Reality note (2026-10-10): the provisioned buckets use the `hub-` prefix, not
the `occupymedia-files-blobs` name first written here. The `hub-` names are the
contract now; do not create duplicates.
Verify actual inventory; names here are ownership contracts, not proof of
provisioning. Do not let the bot write directly into oCIS's opaque bucket. It must
upload via authorized WebDAV/CS3 so metadata and ACLs remain coherent. Each
product uses its own scoped credentials; bucket prefixes are not isolation.
The target is **R2-only durable file/media payloads, minimal local metadata/config
allowed, no local payload cache by design**. POSIX oCIS metadata, Matrix
RocksDB/state, Hub metadata rows, identity/config and keys remain local and need
backups. Source files, document bodies, thumbnails, extracted full text, pad
revisions, attachments and staging/cache copies are payloads. Calling pad text
or full-text extraction a database column does not turn it into metadata.
Bounded transient RAM processing is allowed; disk/swap/core-dump payload copies
are not. This phase does not make existing local Matrix bytes disappear.
## Telegram-like group file contract
The shared TypeScript boundary is `packages/core/src/shared-files.ts`; future
HTTP/DB adapters must validate these shapes at runtime. Use stable
`workspaceId + groupId` (existing Hub groups) and immutable `roomId` to map to an
oCIS `spaceId + folderId`. Start with one restricted Space per group; allow
multiple rooms to map explicitly to that group. Never infer identity/ACL from
room names, aliases or bridge membership. Membership changes must reconcile
both Matrix and file grants; mismatches suspend sharing/mirroring until corrected.
A `GroupFile` records room/group/workspace, sender MXID (not a display name),
Matrix event ID, origin (`matrix`, provider-described `bridge`, direct `upload`,
or `pad-export`), original name, MIME, verified byte size, SHA-256, UTC ISO
`createdAt` and `indexedAt`, stable library resource/version, share link,
retention and availability. For bridged sources preserve both Matrix sender
and provider/remote event attribution when supplied by the trusted bridge;
do not invent attribution. Original `createdAt` comes from event time or accepted
upload time, not the replay/index time. Direct uploads and exports use null event
IDs until a room announcement supplies one. Hash actual bytes while streaming;
S3 ETags, claimed Matrix MIME/size and filenames are not proof. Normalize a safe
display filename and sniff MIME; reject over-limit transfers, including when
Content-Length is missing or lies.
Default links require current group membership; downloads/edit launches must
recheck ACLs and retention. Public links require an explicit group grant and
expiry; revoke them on removal. Persist library links/share IDs and expiry, not
presigned object URLs, WOPI tokens or passwords. Keep retention policy separate
from link expiry: `keep` is the phase-one default with no automated purge;
`until` requires an explicit timestamp, `legalHold` prevents deletion.
Future policy must account for source Matrix media and library copies separately;
one message redaction does not authorize deleting a blob used by another record.
## Metadata index and hub-archive seam
Future `chat_media_archive` persistence is metadata-only. Required constraints:
unique `(workspace_id, room_id, event_id)` for attachment events, stable file
identity/version, indexes on `(workspace_id, group_id, created_at, id)`,
sender, MIME class, name and checksum. List/search return only available,
unexpired rows visible through current group/file ACLs. Use a `(createdAt,id)`
cursor scoped to workspace/group. Filename/MIME/sender/date/origin queries and
oCIS metadata search are the initial searchable/retrievable media experience.
Full-text/OCR/Tika is deferred: neither a local text index nor an extra JVM is
silently included. Archive rows never contain file bodies or encrypted-file keys.
`HubArchivePort` defines future archive/revoke operations. A plain sync-loop bot
(not an appservice) uses a narrowly scoped Matrix account and a scoped oCIS
service identity. Required processing contract:
1. Load an explicitly enabled group mapping and current room encryption state.
Exclude own bot/reply events to prevent loops. Abort on unknown state, lost
membership/ACL, redaction, encrypted events or `content.file` attachments.
2. Accept only `m.room.message` attachments (`m.file`, `m.image`, `m.audio`,
`m.video`) with a valid `mxc://` URI. Use authenticated Matrix media download
endpoints (with version negotiation), never arbitrary user-supplied HTTP URLs.
Validate any redirect to the configured R2 endpoint and drop Matrix credentials
before following; never forward bearer auth to the object store.
3. Claim `attachmentArchiveKey(workspace,room,event)` durably. Use a deterministic
upload destination/resource lookup and reconcile partial retries to avoid
duplicate files across crashes. Stream bounded bytes/hash to WebDAV/CS3;
**no temp files on local disk**. Record the committed library reference,
verified metadata and a durable reply/outbox entry before advancing the
stored Matrix sync cursor. Advancing past a failed event requires a durable
retry record; R2/ACL failures must not lose attachments.
4. Reply once with an authenticated file link using a stable Matrix transaction
ID derived from the replay key. Search is an index query; retrieve by library
resource through current ACLs. An encrypted Tactical Media Broadcast Hub library copy, if
needed for publishing, goes through the existing encryption path into its
own bucket; no cross-product object-key reuse.
5. On redaction/retention or membership removal, revoke links and mark the index
unavailable; stop serving immediately. A later audited deletion workflow
handles library versions/trash, references, source media and legal holds.
Backfill, retries and live sync obey the same policy and idempotency rules.
No implementation of this loop or SQL table is present in phase one; config's
`HUB_ARCHIVE_*` variables reserve its interface. `canArchiveAttachment` is a
tested pure eligibility check, not complete authorization or byte validation.
## Encryption policy
**Encrypted Matrix rooms cannot be server-side mirrored by this architecture.**
No bot encryption keys, decryption engine or silent downgrade is added. Group
admins designate a separate **unencrypted archive room**, tell members its
attachments enter the shared library, and enable its immutable room mapping
explicitly. Users may deliberately upload/share a file there; encrypted rooms
remain untouched. Treat bridged rooms the same: verify encryption state instead
of assuming the bridge made them plaintext. If an archive room becomes encrypted,
or its state cannot be established, mirroring stops. R2 can retain encrypted
Matrix media as ciphertext in a future Tuwunel deployment, but that does not
make it server-searchable or give the bot access to plaintext.
## Collaborative ODT and pad boundaries
ODT editing is **oCIS file -> collaboration WOPI host -> Collabora -> save/version
back through oCIS -> R2**. Matrix posts links and notifications; it does not edit
ODT. The editor gets short-lived, permission-scoped launch tokens and file locks.
Test simultaneous edits, reconnects, saves, versions and grant revocation; keep
Collabora's working files in transient RAM and block launch if that cannot be
guaranteed for the selected image.
`SharedPad` / `PadExportPort` allow Etherpad- or HedgeDoc-compatible group pad
links and revision exports. Pad content, revisions and attachments must be
durable R2 payloads; local storage may retain only identifiers, ACLs and session
metadata. Standard pad deployments typically persist document text/revisions
in their database: they need a separately validated R2 persistence adapter or
another compliant service. R2 attachment support or periodic exports alone is
insufficient. Consequently there is **no running pad service** in this scaffold,
and `PADS_ENABLED=false`. Approved exports become ordinary versioned oCIS
resources with checksums and group retention; live ODT collaboration remains
Collabora's responsibility. This preserves the lightweight pad option without
introducing an incompatible local payload database.