feat(media): service contracts between media slices

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
This commit is contained in:
Claude 2026-10-03 18:11:04 +00:00
parent 475710e27e
commit 2556ed197e
No known key found for this signature in database

View File

@ -11,10 +11,30 @@ import type {
MediaSourceProvider,
MotionGraphicsRenderer,
} from "@rakazo/adapter-kit";
import type { ModelTier } from "@rakazo/contracts";
import type {
AiAlteration,
ClientMediaItem,
CreateMarkInput,
Edl,
EdlError,
FfprobeReport,
GateReport,
IntroVariant,
MarketingCopy,
MarkTreatment,
MediaEditPlan,
MediaMark,
MediaPublication,
MediaRender,
MediaRenderOutput,
MediaRenderQuality,
ModelTier,
PlatformProfile,
Utm,
} from "@rakazo/contracts";
import type { MediaRepos, PrismaClient } from "@rakazo/db";
import { getLogger } from "@rakazo/logging";
import type { ProfileSet } from "@rakazo/media";
import type { CompileSource, ProfileSet } from "@rakazo/media";
import type { z } from "zod";
import { aiRecipient } from "./ai-consent.js";
@ -184,3 +204,152 @@ export function createMediaModelClient(deps: {
},
};
}
// ---------------------------------------------------------------------------
// Service contracts between media slices. Each slice implements its part; the
// API, worker, and agent tools only depend on these interfaces.
// ---------------------------------------------------------------------------
export type CreateMarkInputType = z.infer<typeof CreateMarkInput>;
export interface MediaActor {
spaceId: string;
userId: string;
}
export interface MediaIngestService {
/** Client-pushed metadata (Apple Photos, iOS): upsert, apply deletions, enqueue work. */
pushItems(
actor: MediaActor,
input: {
sourceId: string;
items: ClientMediaItem[];
deletedExternalIds: string[];
syncToken: string | null;
complete: boolean;
},
): Promise<{ created: number; updated: number; unchanged: number; deleted: number }>;
/** Enqueue an incremental sync of a server-side source. */
requestSync(actor: MediaActor, sourceId: string): Promise<void>;
/** An upload finished: move the file into place and enqueue proxy and analysis. */
uploadCompleted(
actor: MediaActor,
input: { assetId: string; tempPath: string; sha256: string; fileName: string },
): Promise<void>;
recluster(actor: MediaActor): Promise<void>;
/** Remove a source with its assets and every derived file. */
removeSource(actor: MediaActor, sourceId: string): Promise<void>;
}
export interface MediaPreparedSources {
sources: Map<string, CompileSource>;
/** Alterations applied while preparing (inpaint, synthetic identity, voice). */
aiAlterations: AiAlteration[];
/** Redactions that could not be honored and fell back, for the plan rationale. */
fallbacks: string[];
}
export interface MediaPrivacyService {
/**
* Resolve every clip's source file and apply all face, mark, and region
* redactions from the EDL plus person and mark defaults. Fails closed: a
* redaction that cannot be applied throws instead of returning an unredacted source.
*/
prepareSources(input: {
actor: MediaActor;
projectId: string;
edl: Edl;
quality: MediaRenderQuality;
workDir: string;
signal?: AbortSignal;
}): Promise<MediaPreparedSources>;
/** Re-detect on the rendered output and evaluate the full gate. */
gate(input: {
actor: MediaActor;
projectId: string;
edl: Edl;
profile: PlatformProfile;
outputPath: string;
ffprobe: FfprobeReport;
copy: { title: string | null; caption: string | null; tags: string[] };
bundleHasDisclosure: boolean;
autoFixRound: number;
signal?: AbortSignal;
}): Promise<GateReport>;
/** Create a mark: segment from the prompt, propagate, score, and suggest a fallback. */
createMark(actor: MediaActor, input: CreateMarkInputType): Promise<MediaMark>;
updateMark(
actor: MediaActor,
input: {
markId: string;
treatment?: MarkTreatment;
label?: string | null;
personId?: string | null;
},
): Promise<MediaMark>;
/** Delete a person, their biometric data, and their files. */
removePerson(actor: MediaActor, personId: string): Promise<void>;
}
export interface MediaRenderService {
start(
actor: MediaActor,
input: { planId: string; quality: MediaRenderQuality },
): Promise<MediaRender>;
cancel(actor: MediaActor, renderId: string): Promise<MediaRender>;
/** Absolute path of a finished output for streaming, or null. */
outputPath(actor: MediaActor, renderId: string, fileName: string): Promise<string | null>;
}
export interface MediaMarketingService {
autoPlan(
actor: MediaActor,
input: { projectId: string; profileIds?: string[] },
): Promise<MediaEditPlan[]>;
submitPlan(
actor: MediaActor,
input: {
projectId: string;
edl: unknown;
rationale: string | null;
createdBy: "agent" | "user";
},
): Promise<{ ok: true; plan: MediaEditPlan } | { ok: false; errors: EdlError[] }>;
introVariants(
actor: MediaActor,
input: { brandKitId: string; count: number },
): Promise<IntroVariant[]>;
thumbnails(
actor: MediaActor,
input: { renderId: string; count: number },
): Promise<MediaRenderOutput[]>;
copy(actor: MediaActor, input: { renderId: string; language: string }): Promise<MarketingCopy>;
createPublication(
actor: MediaActor,
input: {
renderId: string;
destination: string;
targetUrl: string | null;
utm: Utm | null;
title: string | null;
caption: string | null;
tags: string[];
thumbnailVariant: string | null;
scheduledAt: string | null;
},
): Promise<MediaPublication>;
bundle(actor: MediaActor, publicationId: string): Promise<MediaPublication>;
cancelPublication(actor: MediaActor, publicationId: string): Promise<MediaPublication>;
importMetricsCsv(
actor: MediaActor,
input: { mappingId: string; csv: string },
): Promise<{ imported: number; skipped: Array<{ row: number; reason: string }> }>;
}
export interface MediaServices {
deps: MediaServiceDeps;
ingest: MediaIngestService;
privacy: MediaPrivacyService;
render: MediaRenderService;
marketing: MediaMarketingService;
}