Commit graph

91 commits

Author SHA1 Message Date
ad1f4a3bc3 fix: raise upload limits — web dropzone 2 GiB, nginx edge 2g on all vhosts 2026-10-15 09:45:09 +00:00
1ddaf32edf fix: raise upload limits — api 2 GiB, nginx 2g (was 100 MiB api / 20m-512m nginx)
Cloudflare free caps at 100 MB on the proxied route regardless; larger
files need the domain set to DNS-only or a chunked upload path.
2026-10-15 08:46:50 +00:00
3c1bce1cd2 chore: produce-worker debug — MEDIA_DEBUG_STDERR=1 routes ffmpeg stderr to /tmp/ffmpeg-debug.log 2026-10-14 23:19:45 +00:00
d95cee8878 chore: MEDIA_DEBUG_STDERR=1 routes produce ffmpeg stderr to /tmp/ffmpeg-debug.log (debug aid; default stays redacted) 2026-10-14 23:05:10 +00:00
87ddc1a206 fix: api image installs ffmpeg (ffprobe was missing — every upload probe failed ENOENT) 2026-10-14 22:55:02 +00:00
26f3a27eee fix: produce worker boot — scratch dir bootstrap + api ffprobe
The produce worker crashed its claim cycle at the scratch mkdtemp (ENOENT:
the parent dir never existed on a fresh volume) — bootstrap MEDIA_SCRATCH_DIR
recursively at startup. The api image lacked ffprobe entirely, so every
upload's probe job failed — install ffmpeg in the api runtime image.
2026-10-14 22:19:59 +00:00
40e0c49f7c fix: drop unused tmpdir import (lint) 2026-10-14 22:00:15 +00:00
ee8d5c6e76 fix: produce worker claim — SECURITY DEFINER fn fixes 08P01 poll crash + stale-lease reclaim (040)
The store's hand-built claim interpolated the lane predicate into the SQL
text while still passing a bind parameter — node-postgres sent 1 param for
a 0-param statement (08P01) on every poll, so the live produce worker
store-errored every ~2s and never claimed. Migration 040 replaces the
inline claim with produce_job_claim(p_lane, p_stale_after): SECURITY
DEFINER (the 035/oidc_refresh_lookup pattern — the poller is lane-global,
FORCE RLS hides every row without a tenant context), atomic FOR UPDATE
SKIP LOCKED with the queued→transcoding bump as the lease, and stale-
transcoding reclaim so a killed worker's rows return to the queue.
produce_job_workspace(p_job_id) discovers the owning tenant for the
follow-up writes, which stay RLS-bound as sp_app. pg-store rewired; store
errors now surface err.message instead of err.name; pg-store tests added.
38/38 producer tests green.
2026-10-14 21:55:41 +00:00
d9e8cdf789 feat: produce worker deployment + web producer/collective/branding UI + mobile CSS (wave-2c)
Deployment (t4g2): media-worker gains font-dejavu (drawtext lower-thirds)
and a dedicated produce-worker compose service running src/produce.ts —
claims video_processing_jobs compose|mix by lane, renders assembled/
overlaid/voiced cuts, registers derived library assets. Deliberately its
own process so a remote-lane box needs only Postgres + S3 + ffmpeg.

Web: compose media attachments UI (t2), Producer panel in the video
drawer (t5), collective creation + branding settings + shell branding
(brandweb), mobile CSS (mcss: touch targets, icon sizing, safe areas).
2026-10-14 20:40:49 +00:00
1183d69849 feat: compose media attachments UI + video producer panel (t2+t5)
Compose: upload picked files to /media with progress, thumbnails via the
auth-scoped serve route, per-attachment alt text, remove, ≤4 cap with the
Mastodon norm hint, in-flight blocking. Video drawer: Producer section —
lower-third editor, logo key field, MediaRecorder voiceover (record/
stop/review/re-take/upload), produce submission with lane + label, job
progress, derived-asset link. Local types for both contracts.
2026-10-14 20:14:20 +00:00
71db0a08b2 feat: media attachments backend + producer job intake (t1+t3)
Rich media (t1): media_assets (migration 038) + /media module — multipart
upload with magic-byte sniffing, size caps, auth-scoped serve with
ETag/same-origin; the publishing 'text only' gate lifted to accept
0..4 validated mediaAssetIds with optional positional altTexts.

Producer intake (t3): video_processing_jobs gains kind compose|mix +
input_json + produced_asset_id (migration 039), video_assets.derived_from;
POST /video/:id/produce validates overlays/voiceover strictly and queues
the job for the media-worker runner (landing separately).
2026-10-14 20:00:31 +00:00
abb343284a chore: relocate nemotron RFP proposal to the research reports dir 2026-10-14 19:11:34 +00:00
94318de519 fix: branding patch drops asset keys + rejects uppercase hex
validateBrandingPatch validated storage-key values but never assigned
them into the output — PUT /workspace/branding with a logo/background
silently did nothing, so admins could never set them. Also ACCENT_HEX
gains /i (uppercase hex accepted, canonicalized to lowercase on read).
Tests unpinned from the buggy behavior; 41/41 green.
2026-10-14 18:57:00 +00:00
4b249e3811 fix: reconcile branding tests + chat-display types (integration of wave agents) 2026-10-14 18:07:26 +00:00
cbdb8c0f81 feat: POST /auth/workspaces — create an additional collective from an existing account
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.
2026-10-14 17:16:17 +00:00
177ca6eb2b feat: video library imports from Drive and chat media (WS2 backend)
POST /video/import-drive {resourceId} pulls through the oCIS WebDAV
service-account client; POST /video/import-chat {mxcUri} pulls from the
Matrix content repository (MATRIX_MEDIA_TOKEN, homeserver falls back to
ARCHIVE_PROVISIONER_HOMESERVER_URL). Both cap at 256 MiB (buffered v1,
streaming follow-up), store through the same encrypted content-
addressed path as uploads, probe, and audit video.imported with
provenance.
2026-10-14 14:16:19 +00:00
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
1069f20c54 brand: de-ownCloud the files load flash — title, theme-color, splash at the edge
oCIS web's index.html carries ownCloud branding before the app mounts
(title 'ownCloud', blue theme-color, white/blue splash spinner). The
files vhosts (tunnel + TLS) now rewrite those at the edge: Hub title,
dark theme-color, dark splash with warm text and the red spinner.
2026-10-14 13:32:00 +00:00
ced5e5da63 docs: Live Control Room — multiview, switcher, audio, overlays
The engineering guide still called the control room 'planned' while it
ships: multiview grid (2x1-4x4/custom, 4 live HLS tiles, tap-to-focus,
adaptive preview polling), broadcast switcher (PST/TAKE/CUT semantics,
lock, override modes), per-slot + master audio, overlay editor
(lower_third/chyron/logo/clock, layers, rendered revisions), signed
previews and the transactional director behind it. User manual gets the
operator walkthrough (§7.1); engineering guide documents the media-
control architecture and corrects the Level-2 status section; admin
guide gets permissions, PUBLIC_ORIGIN, capacity and tally notes.
2026-10-14 12:17:24 +00:00
b8671113f5 docs: publish governance, governed boosts, receipts, OIDC phase 2, files SSO
Engineering guide: session/refresh/logout architecture, publish-governance
and amplify gating sections, file map to migrations 001-034. Admin guide:
governance administration, files-SSO operations, verification harnesses,
migration list + deploy order + 034 session note. User manual: calendar
receipt badges, boost gating, connections governance note (replaces the
stale 'stub' statuses). Design doc 2026-10-13: marked implemented with the
as-shipped phase 2. Wiki (EN/ES): Drive sign-in + boosts/approvals.
README: governance + Files SSO bullets.
2026-10-14 11:52:48 +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
ea1a9bb4e5 e2e: brand palette guard — fails on blue-family colors in the embedded surfaces
OKLCH-based: any computed color with hue 200–300° and chroma > 0.006 on
/chat or /files/drive (both iframes included) fails the check. Run after
Element/oCIS image bumps; PLAYWRIGHT_PATH can point at an existing
node_modules. Found by the branding panel; threshold from the brand spec.
2026-10-13 19:42:06 +00:00
a8c691924d e2e: S9 governed boost scenario in the governance sim
S9: boost draft (providerMetadata.sourceRemoteRef) → immediate-rule approve
→ POST /publishing/drafts/:id/amplify { connectionId, kind: repost } →
verify the source status reblogged on Mastodon. ONLY=S9 runs it standalone.
Gap list updated: boost + Temporal-queue + status-read gaps closed
(auto-publish remains a governance-model decision, not a missing route).
2026-10-13 19:40:01 +00:00
7aa99681dd files: allow api.occupymedia.cc in the oCIS CSP connect-src
The oCIS web fetches the OIDC discovery document from the Hub issuer in the
browser — connect-src only allowed same-origin + blob, so the fetch was
blocked and the web fell back to its own login. With the CSP fix + the
external issuer flip, the full SSO chain works: Hub login -> authorize ->
consent SPA -> code -> autoprovisioned personal drive.
2026-10-13 19:07:32 +00:00
3117e1c18e ops: OIDC issuer deployment wiring — key volume, CORS for files, Drive authMode flip 2026-10-13 18:46:28 +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
3463b0a4f4 edge: strip Accept-Encoding on the oCIS signin restyle location
oCIS compresses the idp sign-in HTML, and sub_filter cannot rewrite
compressed bodies. Works on the grey/failover path where the edge serves
files; in tunnel mode files bypasses the edge entirely (tunnel ingress
points straight at oCIS), so the permanent brand fix remains the Hub OIDC
issuer (docs/design/2026-10-13-files-oidc-sso.md).
2026-10-13 17:07:50 +00:00
9d42d61f6b edge: restyle the oCIS idp sign-in wall into the Hub palette
The sign-in page is served by oCIS's idp service, which the web theme.json
cannot touch. Inject hub-signin.css via sub_filter on /signin/ until oCIS
sits behind the Hub OIDC issuer (see docs/design/2026-10-13-files-oidc-sso.md).
2026-10-13 16:57:38 +00:00
5bc81c16f2 docs: files SSO design pass — Hub as the OIDC issuer for oCIS
Phase 1 kills the navy login wall (provider + PKCE + auto-consent for the
first-party oCIS web client); phases 2-3 move uploads into personal drives
and share collective folders into them. Mirrors the Matrix JWT broker: the
Hub is the identity, oCIS is a relying party.
2026-10-13 16:52:33 +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
af93176bbe ops: tuwunel loads appservice registrations via TUWUNEL_APPSERVICE_DIR
Inline [appservice.*] tables drop the namespaces field (registration then
fails M_EXCLUSIVE for every AS user). The env var points tuwunel at the
/appservices mount (telegram + hub YAMLs, both with namespaces).
2026-10-13 16:05:48 +00:00
189cb41a09 ops: pin shared Temporal publishing queue; pass MATRIX_SSO_AS_TOKEN/HS_TOKEN to api
W1's governance sim found approved publishes never executing: the api starts
workflows on tmbh-publishing while the 3-day-old scheduler image consumed the
old superposter-publishing default. Pin TEMPORAL_TASK_QUEUE on both services.
2026-10-13 15:35:49 +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
a2aa70c961 edge: social.occupymedia.cc vhost — Mastodon web :3030 + streaming :4040 (CT-host LAN bind, websocket upgrade, 80m uploads) 2026-10-13 14:34:06 +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