The Instagram connector existed but was never registered, so Channels showed
it as "provider unavailable" and publishing could not resolve it.
- Register createInstagramConnector in the connections registry and the
publishing registry; add the connector-runtime publishing policy
(idempotency "none": media_publish has no key, never replay).
- The Facebook app credentials identify the Hub, not a collective: read them
only from INSTAGRAM_CLIENT_ID / INSTAGRAM_CLIENT_SECRET. Drop the
clientId/clientSecret connect fields and stop copying the app secret into
each connection's vault entry. The provider is connectable only when both
are set (connectEnabled + connectNote).
- Fail the connect when several Instagram accounts are granted instead of
silently publishing as the first Page's account.
- Fix the connector package's pre-existing lint errors (it now ships in api).
- Tests: contract cases for env-only credentials and the multi-account
refusal; connections integration case for provider gating and the
Facebook dialog redirect.
- docs/oauth-connect.md: Instagram setup, callback URI and limits.
Not in this change: publishing images needs a public plaintext JPEG URL that
Meta downloads, and storage is client-side encrypted, so presigned S3 URLs
cannot serve it. Until a short-lived media endpoint exists, publishing to
Instagram fails with an explicit error. Token refresh via ig_refresh_token
on graph.facebook.com also needs checking against Facebook Login tokens.
Until now collectives only existed at registration; an account with a
personal workspace had no path to start one. The creator becomes Owner
and gets a session scoped to the new workspace.
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).
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.
MIT licensed. See README.md and docs/.