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,
createConnectorStack,
createJobReconciler,
createMessagingContextLoader,
createMessagingTeamChatSender,
createMediaJobHandlers,
createMediaServices,
createMessagingContextLoader,
createMessagingTeamChatSender,
createProfileConsolidationScheduler,
createRunExecutor,
createRunSandbox,
@ -94,6 +94,7 @@ import { cors } from "hono/cors";
import type { AppEnv } from "./env.js";
import { loadEnv } from "./env.js";
import { mountLocalSettings } from "./local-settings.js";
import { mountMediaHttpRoutes } from "./media-http.js";
import {
createMessagingInboundHandler,
teamChatSenderCanWakeMessageRoutines,
@ -112,7 +113,6 @@ import {
TEAM_CHAT_STARTUP_SHUTDOWN_MS,
} from "./team-chat-startup.js";
import { mountTradingBotStatus } from "./trading-bot-proxy.js";
import { mountMediaHttpRoutes } from "./media-http.js";
import { mountVoiceHttpRoutes } from "./voice.js";
import { mountWebhookHttpRoutes } from "./webhook.js";
@ -455,7 +455,9 @@ export async function createApp(
await graphileWorkerHost.start(jobHandlers);
}
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
? createJobReconciler({

View File

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

View File

@ -77,6 +77,7 @@ export * from "./mcp-oauth.js";
export * from "./mcp-server-tool.js";
export * from "./mcp-transport.js";
export * from "./media-brand.js";
export * from "./media-consent.js";
export * from "./media-context.js";
export * from "./media-emulator.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-folder-source.js";
export * from "./media-folder-source-probe.js";
export * from "./media-gate-autofix.js";
export * from "./media-ingest.js";
export { defaultMediaJobLimits, MediaJobLimiter, type MediaJobLimits } from "./media-jobs.js";
export * from "./media-marketing.js";
@ -103,9 +105,10 @@ export {
MotionTemplateError,
ResvgMotionGraphicsRenderer,
} 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-publisher-bundle.js";
export * from "./media-services.js";
export {
createMediaRenderHandler,
createMediaRenderService,
@ -113,6 +116,7 @@ export {
pickEncoder,
softwareEncoder,
} from "./media-render.js";
export * from "./media-services.js";
export * from "./media-sidecar.js";
export * from "./media-zip.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 { buildTrackingUrl, normalizeHashtags, uploadChecklist } from "@rakazo/media";
import { resolveInside } from "@rakazo/media/node";
import { assertExportConsent } from "./media-consent.js";
import type { MediaActor, MediaMarketingService, MediaServiceDeps } from "./media-context.js";
import type { MediaToolPaths } 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;
if (!profile) throw new MediaStateConflictError(`Profile ${pub.profileId} is not available`);
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 video = outputs.find((output) => output.kind === "video");
if (!video) throw new MediaStateConflictError("Render has no video output");

View File

@ -29,6 +29,7 @@ import type {
MediaServiceDeps,
} from "./media-context.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).
@ -573,10 +574,12 @@ async function runRender(input: RunInput): Promise<void> {
return;
}
const fix =
const planned =
round < MAX_AUTO_FIX_ROUNDS
? planAutoFix(edl, { ...report, autoFixRound: round }, alternatives)
: 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) {
await rm(renderDir, { recursive: true, force: true });
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);
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 };
return set;
};