Commit graph

43 commits

Author SHA1 Message Date
d87c4e99e6 fix: provisioner matrix login retries through M_LIMIT_EXCEEDED instead of failing boot
Rapid deploy restarts exhausted the homeserver's per-account login rate
limit; bootstrap now backs off 5s/15s/45s/90s on 429 before giving up.
2026-10-14 13:55:05 +00:00
71ccc1db75 fix: submissions module constructs its own VideoService (root-module provider not importable) 2026-10-14 13:45:15 +00:00
b4ff73ffa8 fix: submissions IT — stray type name under build tsconfig 2026-10-14 13:37:54 +00:00
599a68cc41 feat: anonymous group submission boxes — public intake, CC licence, moderation
WS0 of the video-producer plan: every group can open a public,
accountless upload box (random token, rotatable, disable switch).
Submitters tick the Creative Commons qualifier; credit name and a
licensing contact are optional free text — contact is visible only in
the moderation surface and never copied onto assets. Intake sniffs
magic bytes (mp4/webm/jpeg/png), caps video 200 MiB / photo 50 MiB,
rate-limits 5/hour per salted IP hash (hash never rendered), dedups by
sha256, caps 5 pending per box, and quarantines everything as pending.
Group managers approve (video → video library via the register+probe
path, photo → group asset) or reject with a note; every step audited.
Migration 035 (RLS + submission_box_lookup/submission_rate_count
definer fns). 4 unit + 4 live-DB tests.
2026-10-14 13:32:00 +00:00
643fbb94bc feat: OIDC phase 2 — session-bound refresh tokens, server-side logout
Hub sessions gain an optional sid backed by auth_sessions (login/reissue
write the row; POST /auth/logout revokes it; pre-phase2 tokens are
refused at reissue so revocation applies from the next epoch bump).
The OIDC provider issues rotating refresh tokens (oidc_refresh_tokens,
SHA-256 at rest, family-wide revocation on reuse detection) that live
and die with the consenting Hub session — an oCIS session can no longer
outlive the sign-in that minted it, and Hub logout propagates at the
next refresh instead of the next silent-authorize. Discovery advertises
refresh_token. Migration 034; RLS via the audit_append definer pattern
for the unauthenticated token-endpoint lookup.
2026-10-14 11:33:47 +00:00
d6eb39ff27 feat: gate amplify boosts behind per-connection publish governance
A boost to a gated connection (admin1/admin_majority/admin_consensus)
now holds with awaiting_publish_approval on the same publish-phase poll
the publish route uses, instead of executing the reblog. The holder
re-invokes amplify after the pass (hold-and-retry: no durable amplify
queue exists and the poll-close transaction must not do network I/O).
Unset/auto connections behave exactly as before; the route answers 202
on hold, 200 on execute. Adds amplify-governance IT suite (3 live-DB
tests).
2026-10-14 10:26:27 +00:00
b89dea5c93 feat: per-connection publish governance — auto, 1-admin, majority, consensus
Per the owner's spec: each social connection gets publishGovernance in its
fields — unset = manual publish (today), auto = dispatch on approval,
admin1/admin_majority/admin_consensus = a publish-phase chatgov poll
(subject draft_publish, its own open-poll slot, no collision with review
gates). Dispatch holds until the publish poll passes (E3 pre-dispatch
check); auto dispatches on approval. Includes migration 033, the PATCH
/connections/:id/publish-governance route (chatgov.manage), and 19+31 new
tests. Implementation by the branding-panel process (claude/opus), lint
and integration fixes here.
2026-10-14 08:04:56 +00:00
f2eb14956e brand: front-page header — mark only, the red wordmark carries the name
The header double-rendered 'Tactical Media Broadcast Hub': white via the
Logo lockup's wordmark, then again in red via the accent span. The mark
alone + the red span is the whole lockup the palette calls for.
2026-10-14 07:32:01 +00:00
96e5a63dd8 publishing: unfenced vault accessor for the amplify path
createPublishingCredentialAccessor is execution-fenced (Temporal contexts
only) — the API-process amplify route threw EXECUTION_CREDENTIAL_FENCE.
Use the established PgConnectionStore+vault accessor instead (same trust
level as the archive amplify route's bot-token path).
2026-10-13 18:32:38 +00:00
6145281b3f publishing: governed amplify route — approved drafts can boost their recorded source
POST /publishing/drafts/:draftId/amplify { connectionId, kind?: repost|quote }:
the governance gate is the draft state (only approved drafts amplify), the
target is providerMetadata.sourceRemoteRef recorded at creation — never a
caller-supplied id. Connectors resolve from a process-level publishing
registry (vault-backed credentials); capability-checked per kind. Closes
the gap the governance sim documented (boosts fell back to replies).
2026-10-13 18:18:18 +00:00
c01d121748 mobile: gateway handoff for Chat and Drive below 1024px; pin Hub theme in the SSO bootstrap
Panel consensus (the branding panel): don't mount full-app iframes on
mobile — nested scrolling, keyboard collapse and double chrome break both
Element and oCIS under the bottom tab bar. Chat and Drive render a gateway
card (same-tab handoff, matrix: deep link, signed-in-as) below 1024px; the
iframe stays on desktop. hub-sso.html pins mx_local_settings.theme to
custom-OccupyHub Dark once (version-flagged) — a device's earlier picker
choice would otherwise beat config.json's default_theme.
2026-10-13 17:41:05 +00:00
dae302bd18 brand: process-not-platform manifesto on the landing
Hero strip (ES/EN): 'Not a platform. A process.' / 'Decide together.
Publish anywhere. Forget nothing.' / 'What they delete, we archive. What
they silence, we broadcast.' Footer line: 'Not a product. A process.
Built by collectives, for collectives.'
2026-10-13 16:44:59 +00:00
a3cf150494 brand: unclip sidebar wordmark, styled dark 404, human files error, Element brand text
From the brand review: the sidebar truncated 'Tactical Media Broadcast Hub'
on eleven desktop routes; unknown routes rendered a stock white Next.js 404;
solo users saw the raw 'groupid must be a UUID' validation text on Files;
Element's config brand string still read 'OccupyMedia Hub'.
2026-10-13 16:25:48 +00:00
11f75e945e revert: matrix-sso to the JWT broker flow; tuwunel migrated to [global] profile
tuwunel's login/get_token demands device-bound UIA completed with
org.matrix.login.jwt — the AS-impersonation shortcut cannot mint login
tokens. The [global] profile migration activates [global.jwt] (register_user
+ JWT UIA), so the original flow works: JWT login (auto-registers
@hub_<user>) -> get_token with JWT UIA -> logout broker device.
2026-10-13 16:13:51 +00:00
4a0d398a76 fix: Matrix SSO via dedicated hub appservice (AS register + get_token impersonation)
The live tuwunel does not offer m.login.jwt (the jwt.* flat config keys were
inert), so the JWT broker flow could never work. Pivot: appservice.hub owns
the exclusive hub_* namespace on the homeserver; POST /matrix/session now
registers @hub_<username> with the as_token and mints a single-use
m.login.token via login/get_token (?user_id= impersonation). Optional
MATRIX_SSO_HS_TOKEN authenticates inbound appservice transactions, acked by
the new MatrixAppserviceController sink. Response shape unchanged.
2026-10-13 15:29:16 +00:00
fe5f2403d6 feat: in-app Chat (Matrix JWT SSO) + Drive (embedded oCIS)
- api: matrix-sso module (tuwunel jwt -> login/get_token), POST /matrix/session, GET /matrix/identity, drive controller
- web: /chat full-height Element view, /files Library+Drive tabs, embedded-app frame component
- edge: /hub-sso.html handshake route (origin-pinned CSP), X-Frame-Options strip on files
- oCIS: csp.yaml frame-ancestors app.occupymedia.cc
- server config still required: tuwunel [global.jwt], MATRIX_SSO_JWT_SECRET, oCIS restart
2026-10-13 14:57:07 +00:00
8cc1c3b634 Login: drop AuthProvider dependency so /login exports statically 2026-10-13 13:18:23 +00:00
22f9e12141 Login: bare sign-in page at /login; Telegram bridge connection guide 2026-10-13 09:49:57 +00:00
d313ce0bec Onboarding: wide hero banner (mark left, system name right) on black 2026-10-13 08:23:52 +00:00
27fd68c8df Landing: Enter -> login door, /login deep-link (signin mode), /signup register; onboarding scroll fix 2026-10-13 08:13:33 +00:00
f98fca45d0 oCIS upload: drain to buffer + explicit content-length (reva rejects chunked PUTs) 2026-10-13 05:47:19 +00:00
c918fb984e group-files test: expect the hub-groups root MKCOL before the per-group folder 2026-10-13 05:37:15 +00:00
dec505b83e oCIS: create the hub-groups root before the per-group folder (409 on missing parent) 2026-10-13 05:34:10 +00:00
cbbe713dea 032: accept bare room IDs (tuwunel 1.9.4 mints and serves them without :server) 2026-10-12 23:10:59 +00:00
fd09967851 Provisioner: retry onboarding posts (fresh-room forward-extremities race), degrade non-fatally 2026-10-12 22:57:00 +00:00
27a7b72984 Provisioner: normalize bare room IDs to full !id:server form (tuwunel returns localpart) 2026-10-12 22:47:48 +00:00
e3a6e9da97 Share with collective: fix union narrowing in open-room result handling 2026-10-12 22:35:32 +00:00
df54cb9b9e Provisioner: never list the room creator in power-level users (spec rejects it) 2026-10-12 21:46:55 +00:00
b984d6842f Files: mobile upload button, no-collective empty state; nav chat/approvals icons 2026-10-12 18:02:56 +00:00
3f61d11072 /signup: stable account-creation door (redirects to register-mode onboarding) 2026-10-12 16:32:05 +00:00
2a3a1d79a1 P1 flow fix: register makes a solo user by default, collective creation is an opt-in flag, post-login menu for collective-less accounts 2026-10-12 16:19:08 +00:00
2665f43cc4 Sidebar: full icon set (new chat + approvals marks), Files/Chat elevated, settings wheel 2026-10-12 16:12:10 +00:00
6161ac4740 Manual: full participation guide (media/accounts/decisions/safety) + Enter-to-door + provisioner env passthrough 2026-10-12 15:49:25 +00:00
4a7e78b28d Collectives P1: participation landing (ES/EN), group tree, room provisioning APIs
- 030: groups.parent_id tree + kind discriminator + group_rooms mapping
- /collectives module: create/tree/join APIs, MatrixProvisioner (open room
  public+federated, closed room E2EE-at-creation, onboarding room), NullProvisioner
  fail-soft when ARCHIVE_PROVISIONER_* unset
- web root becomes the public participation manual (ES-first + EN toggle,
  three paths: join by invite, start collective, solo account)
- participation manual content in core
2026-10-12 13:53:40 +00:00
39dcf7a8b8 Edge: chat root redirects to branded client, serve matrix well-known; onboarding hero full-bleed on black 2026-10-12 12:55:05 +00:00
699b9412c1 Phase B: encrypted archive export bundles (age), power-level-gated export command, encryption-state linter 2026-10-12 12:46:56 +00:00
e6a78a0805 Hub nav: Chat tab (branded self-hosted Element at element.occupymedia.cc) 2026-10-12 09:13:37 +00:00
7ea0b1e137 Phase two: archive origin facet, redaction-aware reads, share revocation on redaction
- 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
2026-10-11 21:44:23 +00:00
2cb77ae6b4 Harden archive bot for production; exempt container egress in edge firewall
- archive_sync_state keyed by room per migration 029 (drop synthetic id cursor)
- putStaging buffers the stream and sends content-length (oCIS rejects chunked)
- mimeClass passthrough from sniffed payload
- edge-firewall: RETURN docker-bridge ingress before the CF-range catch-all so
  container egress (Matrix/R2/platform) survives the 80/443 source filter
- files scaffold: keep the ocdav data-gateway hop on-loopback
2026-10-11 16:12:43 +00:00
4bf90ffe25 Complete files UI rewiring to the deployed api surface; expose mimeClass
page.tsx now calls the real backend: GET /files with groupId/name/mimeClass/
cursor/limit (origin filter dropped — the api has no origin param), shares
tracked as ShareCreated (revoke by id), retention mapped to the orthogonal
legalHold model, downloads by item.name, samples reshaped to the wire
contract.

GroupFile gains mimeClass (the indexed coarse class the row icon and the
filter depend on): core contract, service row mapper, parseGroupFile guard,
test fixture, UI types — one vocabulary end to end. parseGroupFile's roomId
becomes null-tolerant to match direct-upload rows (mapping input stays
strict).

Verified: core build clean, api typecheck 0 errors + group-files 22/22,
web typecheck 0 errors.
2026-10-11 11:08:08 +00:00
e7e703c105 Integrate group files: nullable room for uploads, archive class, claim idempotency, upload route
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).
2026-10-11 10:09:24 +00:00
d58db63fe6 Add group cloud: files index api, web UI, archive sync bot
The Telegram-like group file contract (packages/core shared-files.ts) becomes
a working subsystem, built in three parallel tracks:

API (NestJS): migration 029 group_files (chat_media_archive metadata-only
index with partial replay-unique + RLS, group_file_shares with sha256 token
persistence, archive_sync_state/outbox durable-claim seam incl. a unique
claim_key index), group-files module (list/search with cursor pagination,
direct upload ≤20 MiB streamed+hashed to oCIS WebDAV, download with ACL +
retention rechecks, share links with expiry, retention modes incl.
legalHold) — RBAC group-membership authz + audit chain events.

Web: Files section per group — group picker, drag-drop upload (20 MiB cap),
search + MIME/origin filters, cursor pagination, download via authenticated
blob fetch, share dialog with expiry + shown-once token, admin retention
dialog; bilingual EN/ES (wiki i18n pattern); nav sidebar/palette/bottom-nav
wiring; MIME classes aligned to the DB CHECK (document/other).

Archive bot (apps/archive-bot): plain sync-loop Matrix bot mirroring
attachments from explicitly-mapped unencrypted archive rooms — raw /sync
with durable since-cursor, outbox-claimed ingestion (hash-verified stream,
no temp files), magic-byte MIME sniffing, safe-name normalization, WebDAV
upload, indexed + replied with link, redaction→unavailable; health endpoint;
Dockerfile. Tests 74/74, typecheck clean across api/web/bot.
2026-10-11 08:21:11 +00:00
aa5e262f3a Tactical Media Broadcast Hub — initial release
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/.
2026-10-10 11:56:42 +00:00