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/.
This commit is contained in:
glass beadian 2026-10-10 11:56:42 +00:00
commit aa5e262f3a
880 changed files with 111042 additions and 0 deletions

View file

@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { publishingIdempotencyKey } from "@tactical-media-broadcast-hub/core";
import { canonicalPostHash } from "./approval.js";
describe("approval and attempt identity", () => {
it("sorts nested keys, excludes execution metadata, and preserves target and array order", () => {
const post = { text: "hi", mediaAssetIds: ["a", "b"], providerMetadata: { connectionId: "target", nested: { z: 2, a: 1 } } };
const reordered = { providerMetadata: { nested: { a: 1, z: 2 }, connectionId: "target", draftId: "injected", idempotencyKey: "key" }, mediaAssetIds: ["a", "b"], text: "hi" };
expect(canonicalPostHash(post)).toBe(canonicalPostHash(reordered));
expect(canonicalPostHash(post)).not.toBe(canonicalPostHash({ ...post, mediaAssetIds: ["b", "a"] }));
expect(canonicalPostHash(post)).not.toBe(canonicalPostHash({ ...post, providerMetadata: { ...post.providerMetadata, connectionId: "other" } }));
});
it("uses decimal bigint attempts without precision loss", () => {
expect(publishingIdempotencyKey({ workspaceId: "ws", draftId: "draft", attemptNo: "9007199254740993" })).toBe("wp3:ws:draft:a9007199254740993:publish");
expect(() => publishingIdempotencyKey({ workspaceId: "ws", draftId: "draft", attemptNo: Number.MAX_SAFE_INTEGER + 1 })).toThrow();
for (const attemptNo of ["0", "01", "-1", "1.1"]) expect(() => publishingIdempotencyKey({ workspaceId: "ws", draftId: "draft", attemptNo })).toThrow();
});
});

View file

@ -0,0 +1,13 @@
import { createHash } from "node:crypto";
function canonical(value: unknown): unknown {
if (Array.isArray(value)) return value.map(canonical);
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)]));
return value;
}
/** The target lives in providerMetadata.connectionId; execution keys are excluded. */
export function canonicalPostHash<T extends { providerMetadata: Record<string, unknown> }>(post: T): string {
const metadata = { ...post.providerMetadata };
delete metadata.idempotencyKey;
delete metadata.draftId;
return createHash("sha256").update(JSON.stringify(canonical({ ...post, providerMetadata: metadata }))).digest("hex");
}

View file

@ -0,0 +1,3 @@
export * from "./publishing.repository.js";
export * from "./approval.js";
export * from "./schema.js";

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
import type { Pool } from "pg";
/** No runtime DDL: code refuses an incomplete or incorrectly privileged schema. */
export async function assertPublishingSchema(pool: Pool): Promise<void> {
const result = await pool.query<{ ready: boolean }>(`SELECT
EXISTS (SELECT 1 FROM schema_migrations WHERE name = '019_publishing_integration.sql')
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'publishing_attempts' AND column_name = 'first_dispatch_started_at')
AND (SELECT count(*) = 2 AND bool_and(relrowsecurity AND relforcerowsecurity) FROM pg_class WHERE oid IN ('publishing_attempts'::regclass, 'publishing_receipts'::regclass))
AND NOT EXISTS (SELECT 1 FROM pg_index WHERE indrelid IN ('publishing_attempts'::regclass, 'publishing_receipts'::regclass) AND NOT indisvalid)
AND current_user = 'sp_app'
AND NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = current_user AND (rolsuper OR rolbypassrls)) AS ready`);
if (result.rows[0]?.ready !== true) throw new Error("PUBLISHING_SCHEMA_NOT_READY");
}