Skip to content

Commit 156cefe

Browse files
committed
feat(producer): version distributed plan protocol
1 parent 05c3029 commit 156cefe

10 files changed

Lines changed: 508 additions & 3 deletions

File tree

packages/producer/src/distributed.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ export {
8282
} from "./services/distributed/renderConfigValidation.js";
8383
export { hashProjectDir } from "./services/distributed/projectHash.js";
8484

85+
// ── Plan protocol compatibility ────────────────────────────────────────────
86+
// Workers validate this descriptor before consuming layout-specific
87+
// artifacts. Missing descriptors remain compatible with legacy v1 plans.
88+
export {
89+
CURRENT_PLAN_PROTOCOL,
90+
DISTRIBUTED_RENDER_CAPABILITIES,
91+
getDistributedRenderCapabilities,
92+
PLAN_ARTIFACT_LAYOUT,
93+
PLAN_HASH_SCHEMA,
94+
PLAN_PROTOCOL_UNSUPPORTED,
95+
PLAN_SCHEMA_VERSION,
96+
PlanProtocolUnsupportedError,
97+
readPlanProtocol,
98+
type DistributedRenderCapabilities,
99+
type PlanProtocolDescriptor,
100+
type PlanProtocolV1Descriptor,
101+
} from "./services/distributed/planProtocol.js";
102+
85103
// ── Format union ────────────────────────────────────────────────────────────
86104
// Canonical output-format type. The aws-lambda package re-exports it so
87105
// CLI / adopter SDKs can derive runtime allowlists from one source.

packages/producer/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,22 @@ export {
133133
// separate subpath import.
134134
export {
135135
assemble,
136+
CURRENT_PLAN_PROTOCOL,
137+
DISTRIBUTED_RENDER_CAPABILITIES,
138+
getDistributedRenderCapabilities,
139+
PLAN_ARTIFACT_LAYOUT,
140+
PLAN_HASH_SCHEMA,
141+
PLAN_PROTOCOL_UNSUPPORTED,
142+
PLAN_SCHEMA_VERSION,
136143
plan,
144+
PlanProtocolUnsupportedError,
145+
readPlanProtocol,
137146
renderChunk,
138147
type AssembleResult,
139148
type ChunkResult,
149+
type DistributedRenderCapabilities,
140150
type DistributedRenderConfig,
151+
type PlanProtocolDescriptor,
152+
type PlanProtocolV1Descriptor,
141153
type PlanResult,
142154
} from "./distributed.js";

packages/producer/src/services/distributed/assemble.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { defaultLogger, type ProducerLogger } from "../../logger.js";
4040
import { formatExportFrameName } from "../../utils/paths.js";
4141
import { padOrTrimAudioToVideoFrameCount } from "../render/audioPadTrim.js";
4242
import type { ChunkSliceJson } from "../render/stages/freezePlan.js";
43+
import { readPlanProtocol } from "./planProtocol.js";
4344
import type { DistributedFormat } from "./shared.js";
4445

4546
/**
@@ -56,6 +57,7 @@ export interface AssembleResult {
5657

5758
/** Shape of the planDir's top-level `plan.json` — only the fields `assemble` needs. */
5859
interface PlanJsonForAssemble {
60+
protocol?: unknown;
5961
planHash: string;
6062
totalFrames: number;
6163
hasAudio: boolean;
@@ -118,10 +120,11 @@ export async function assemble(
118120
if (!existsSync(planJsonPath)) {
119121
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
120122
}
123+
const plan = JSON.parse(readFileSync(planJsonPath, "utf-8")) as PlanJsonForAssemble;
124+
readPlanProtocol(plan);
121125
if (!existsSync(chunksJsonPath)) {
122126
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
123127
}
124-
const plan = JSON.parse(readFileSync(planJsonPath, "utf-8")) as PlanJsonForAssemble;
125128
const chunks = JSON.parse(readFileSync(chunksJsonPath, "utf-8")) as ChunkSliceJson[];
126129
if (chunkPaths.length !== chunks.length) {
127130
throw new Error(

packages/producer/src/services/distributed/plan.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { tmpdir } from "node:os";
2020
import { join } from "node:path";
2121
import { recomputePlanHashFromPlanDir } from "../render/stages/freezePlan.js";
2222
import { RenderQualityError } from "../renderOrchestrator.js";
23+
import { CURRENT_PLAN_PROTOCOL } from "./planProtocol.js";
2324
import {
2425
applyDistributedAudioWarningPolicy,
2526
buildChunkSlices,
@@ -345,6 +346,7 @@ describe("plan() — golden planDir + planHash determinism", () => {
345346

346347
// ── PlanResult contract ─────────────────────────────────────────────
347348
expect(result.planDir).toBe(planDir);
349+
expect(result.planProtocol).toEqual(CURRENT_PLAN_PROTOCOL);
348350
expect(result.planHash).toMatch(/^[0-9a-f]{64}$/);
349351
expect(result.chunkCount).toBe(1);
350352
expect(result.totalFrames).toBe(30); // 1s @ 30fps
@@ -373,6 +375,7 @@ describe("plan() — golden planDir + planHash determinism", () => {
373375
unknown
374376
>;
375377
expect(planJson.planHash).toBe(result.planHash);
378+
expect(planJson.protocol).toEqual(CURRENT_PLAN_PROTOCOL);
376379
expect(planJson.hasAudio).toBe(false);
377380
expect(planJson.totalFrames).toBe(result.totalFrames);
378381
},
@@ -460,8 +463,18 @@ describe("plan() — golden planDir + planHash determinism", () => {
460463
expect(recomputed).toBe(result.planHash);
461464
const planJson = JSON.parse(readFileSync(join(planDir, "plan.json"), "utf-8")) as {
462465
planHash: string;
466+
protocol?: unknown;
463467
};
464468
expect(planJson.planHash).toBe(result.planHash);
469+
expect(planJson.protocol).toEqual(CURRENT_PLAN_PROTOCOL);
470+
471+
delete planJson.protocol;
472+
writeFileSync(join(planDir, "plan.json"), `${JSON.stringify(planJson, null, 2)}\n`, "utf-8");
473+
expect(recomputePlanHashFromPlanDir(planDir)).toBe(result.planHash);
474+
475+
planJson.protocol = CURRENT_PLAN_PROTOCOL;
476+
writeFileSync(join(planDir, "plan.json"), `${JSON.stringify(planJson, null, 2)}\n`, "utf-8");
477+
expect(recomputePlanHashFromPlanDir(planDir)).toBe(result.planHash);
465478
},
466479
TIMEOUT_MS,
467480
);

packages/producer/src/services/distributed/plan.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
readFfmpegVersion,
8080
readProducerVersion,
8181
} from "./shared.js";
82+
import { CURRENT_PLAN_PROTOCOL, type PlanProtocolV1Descriptor } from "./planProtocol.js";
8283

8384
/**
8485
* Caller-supplied configuration for a distributed render. `fps`, `width`,
@@ -254,6 +255,7 @@ export interface DistributedRenderConfig {
254255
*/
255256
export interface PlanResult {
256257
planDir: string;
258+
planProtocol: Readonly<PlanProtocolV1Descriptor>;
257259
planHash: string;
258260
chunkCount: number;
259261
totalFrames: number;
@@ -1084,6 +1086,7 @@ export async function plan(
10841086

10851087
return {
10861088
planDir,
1089+
planProtocol: CURRENT_PLAN_PROTOCOL,
10871090
planHash,
10881091
chunkCount,
10891092
totalFrames,

0 commit comments

Comments
 (0)