feat(media): wave 2 integration

Composition root shared by API and worker, media job handlers on every job
host, HTTP byte routes, consent re-check before bundling, and auto-fix limited
to changes that keep protection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
This commit is contained in:
Claude 2026-10-03 19:04:01 +00:00
parent 90f18cea52
commit b4083844ce
No known key found for this signature in database
6 changed files with 24 additions and 10 deletions

View File

@ -25,10 +25,10 @@ import {
createCloudAgentConnection, createCloudAgentConnection,
createConnectorStack, createConnectorStack,
createJobReconciler, createJobReconciler,
createMessagingContextLoader,
createMessagingTeamChatSender,
createMediaJobHandlers, createMediaJobHandlers,
createMediaServices, createMediaServices,
createMessagingContextLoader,
createMessagingTeamChatSender,
createProfileConsolidationScheduler, createProfileConsolidationScheduler,
createRunExecutor, createRunExecutor,
createRunSandbox, createRunSandbox,
@ -94,6 +94,7 @@ import { cors } from "hono/cors";
import type { AppEnv } from "./env.js"; import type { AppEnv } from "./env.js";
import { loadEnv } from "./env.js"; import { loadEnv } from "./env.js";
import { mountLocalSettings } from "./local-settings.js"; import { mountLocalSettings } from "./local-settings.js";
import { mountMediaHttpRoutes } from "./media-http.js";
import { import {
createMessagingInboundHandler, createMessagingInboundHandler,
teamChatSenderCanWakeMessageRoutines, teamChatSenderCanWakeMessageRoutines,
@ -112,7 +113,6 @@ import {
TEAM_CHAT_STARTUP_SHUTDOWN_MS, TEAM_CHAT_STARTUP_SHUTDOWN_MS,
} from "./team-chat-startup.js"; } from "./team-chat-startup.js";
import { mountTradingBotStatus } from "./trading-bot-proxy.js"; import { mountTradingBotStatus } from "./trading-bot-proxy.js";
import { mountMediaHttpRoutes } from "./media-http.js";
import { mountVoiceHttpRoutes } from "./voice.js"; import { mountVoiceHttpRoutes } from "./voice.js";
import { mountWebhookHttpRoutes } from "./webhook.js"; import { mountWebhookHttpRoutes } from "./webhook.js";
@ -455,7 +455,9 @@ export async function createApp(
await graphileWorkerHost.start(jobHandlers); await graphileWorkerHost.start(jobHandlers);
} }
if (mediaJobs) { if (mediaJobs) {
void mediaJobs.resume().catch((error) => getLogger().error("media ingest resume failed", error)); void mediaJobs
.resume()
.catch((error) => getLogger().error("media ingest resume failed", error));
} }
const reconciler = inMemoryJobs const reconciler = inMemoryJobs
? createJobReconciler({ ? createJobReconciler({

View File

@ -10,10 +10,10 @@ import {
createCloudAgentConnection, createCloudAgentConnection,
createConnectorStack, createConnectorStack,
createJobReconciler, createJobReconciler,
createMessagingContextLoader,
createPostgresReconciliationLeadership,
createMediaJobHandlers, createMediaJobHandlers,
createMediaServices, createMediaServices,
createMessagingContextLoader,
createPostgresReconciliationLeadership,
createProfileConsolidationScheduler, createProfileConsolidationScheduler,
createRunExecutor, createRunExecutor,
createRunSandbox, createRunSandbox,
@ -31,9 +31,9 @@ import {
isPipedreamEnabled, isPipedreamEnabled,
LocalAgentHomeStore, LocalAgentHomeStore,
LocalArtifactStore, LocalArtifactStore,
mediaConfigFromEnv,
McpConnector, McpConnector,
McpOAuthBroker, McpOAuthBroker,
mediaConfigFromEnv,
messagingEnvFromProcess, messagingEnvFromProcess,
messagingPlatformsFromEnv, messagingPlatformsFromEnv,
PiAgentRuntime, PiAgentRuntime,

View File

@ -77,6 +77,7 @@ export * from "./mcp-oauth.js";
export * from "./mcp-server-tool.js"; export * from "./mcp-server-tool.js";
export * from "./mcp-transport.js"; export * from "./mcp-transport.js";
export * from "./media-brand.js"; export * from "./media-brand.js";
export * from "./media-consent.js";
export * from "./media-context.js"; export * from "./media-context.js";
export * from "./media-emulator.js"; export * from "./media-emulator.js";
export * from "./media-emulator-clips.js"; export * from "./media-emulator-clips.js";
@ -85,6 +86,7 @@ export * from "./media-factory.js";
export * from "./media-ffmpeg-renderer.js"; export * from "./media-ffmpeg-renderer.js";
export * from "./media-folder-source.js"; export * from "./media-folder-source.js";
export * from "./media-folder-source-probe.js"; export * from "./media-folder-source-probe.js";
export * from "./media-gate-autofix.js";
export * from "./media-ingest.js"; export * from "./media-ingest.js";
export { defaultMediaJobLimits, MediaJobLimiter, type MediaJobLimits } from "./media-jobs.js"; export { defaultMediaJobLimits, MediaJobLimiter, type MediaJobLimits } from "./media-jobs.js";
export * from "./media-marketing.js"; export * from "./media-marketing.js";
@ -103,9 +105,10 @@ export {
MotionTemplateError, MotionTemplateError,
ResvgMotionGraphicsRenderer, ResvgMotionGraphicsRenderer,
} from "./media-motion-renderer.js"; } from "./media-motion-renderer.js";
export * from "./media-privacy.js";
export { PrivacyUnavailableError } from "./media-privacy-io.js";
export * from "./media-publish.js"; export * from "./media-publish.js";
export * from "./media-publisher-bundle.js"; export * from "./media-publisher-bundle.js";
export * from "./media-services.js";
export { export {
createMediaRenderHandler, createMediaRenderHandler,
createMediaRenderService, createMediaRenderService,
@ -113,6 +116,7 @@ export {
pickEncoder, pickEncoder,
softwareEncoder, softwareEncoder,
} from "./media-render.js"; } from "./media-render.js";
export * from "./media-services.js";
export * from "./media-sidecar.js"; export * from "./media-sidecar.js";
export * from "./media-zip.js"; export * from "./media-zip.js";
export * from "./memory-provider-factory.js"; export * from "./memory-provider-factory.js";

View File

@ -8,6 +8,7 @@ import { InvalidMediaInputError, MediaStateConflictError } from "@rakazo/db";
import { getLogger } from "@rakazo/logging"; import { getLogger } from "@rakazo/logging";
import { buildTrackingUrl, normalizeHashtags, uploadChecklist } from "@rakazo/media"; import { buildTrackingUrl, normalizeHashtags, uploadChecklist } from "@rakazo/media";
import { resolveInside } from "@rakazo/media/node"; import { resolveInside } from "@rakazo/media/node";
import { assertExportConsent } from "./media-consent.js";
import type { MediaActor, MediaMarketingService, MediaServiceDeps } from "./media-context.js"; import type { MediaActor, MediaMarketingService, MediaServiceDeps } from "./media-context.js";
import type { MediaToolPaths } from "./media-folder-source-probe.js"; import type { MediaToolPaths } from "./media-folder-source-probe.js";
import { runMediaTool } from "./media-folder-source-probe.js"; import { runMediaTool } from "./media-folder-source-probe.js";
@ -249,6 +250,8 @@ export async function buildBundle(
const profile = profileSet.profiles.get(pub.profileId)?.profile; const profile = profileSet.profiles.get(pub.profileId)?.profile;
if (!profile) throw new MediaStateConflictError(`Profile ${pub.profileId} is not available`); if (!profile) throw new MediaStateConflictError(`Profile ${pub.profileId} is not available`);
const edl = plan.edl; const edl = plan.edl;
// Consent is re-read here: a withdrawal after the gate passed still stops the export.
await assertExportConsent(deps, actor, { edl, profile });
const outputs = storedRenderOutputs(row.outputs); const outputs = storedRenderOutputs(row.outputs);
const video = outputs.find((output) => output.kind === "video"); const video = outputs.find((output) => output.kind === "video");
if (!video) throw new MediaStateConflictError("Render has no video output"); if (!video) throw new MediaStateConflictError("Render has no video output");

View File

@ -29,6 +29,7 @@ import type {
MediaServiceDeps, MediaServiceDeps,
} from "./media-context.js"; } from "./media-context.js";
import { MediaToolMissingError } from "./media-folder-source-probe.js"; import { MediaToolMissingError } from "./media-folder-source-probe.js";
import { nonCompliantChange } from "./media-gate-autofix.js";
/* /*
* Render service (API side) and render job (worker side). * Render service (API side) and render job (worker side).
@ -573,10 +574,12 @@ async function runRender(input: RunInput): Promise<void> {
return; return;
} }
const fix = const planned =
round < MAX_AUTO_FIX_ROUNDS round < MAX_AUTO_FIX_ROUNDS
? planAutoFix(edl, { ...report, autoFixRound: round }, alternatives) ? planAutoFix(edl, { ...report, autoFixRound: round }, alternatives)
: null; : null;
// An auto-fix may only add protection or cut content, never weaken a redaction.
const fix = planned && nonCompliantChange(edl, planned.edl) === null ? planned : null;
if (!fix) { if (!fix) {
await rm(renderDir, { recursive: true, force: true }); await rm(renderDir, { recursive: true, force: true });
const blocked = await update({ status: "blocked", progress: 1, gateReport: report }); const blocked = await update({ status: "blocked", progress: 1, gateReport: report });

View File

@ -72,7 +72,9 @@ export function createMediaServices(input: {
]); ]);
const set = mergeProfiles(builtin.profiles, own.profiles); const set = mergeProfiles(builtin.profiles, own.profiles);
set.errors = [...builtin.errors, ...own.errors]; set.errors = [...builtin.errors, ...own.errors];
for (const error of set.errors) getLogger().warn("media profile ignored", error); for (const error of set.errors) {
getLogger().warn("media profile ignored", { file: error.file, reason: error.message });
}
cached = { at: Date.now(), set }; cached = { at: Date.now(), set };
return set; return set;
}; };