mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
## What Refactor distributed planning around one shared local execution-plan builder: - `buildLocalExecutionPlan()` now owns compile/probe/extract/audio/freeze. - Legacy `plan()` remains a deprecated v1 transport wrapper. - Plan v2 calls the shared builder directly and publishes through the existing manifest/CAS contract. - Add neutral `createPlanV2FromExecutionPlan()`, `publishPlanV2FromExecutionPlan()`, `getPlanV2ExecutionPlanHash()`, and `PLAN_PROTOCOL_V1` names. - Retain deprecated v1-named exports and wire aliases. - Recommend explicit Plan v2 opt-in for new producer, Lambda, and Cloud Run integrations. ## Why Plan v2 previously looked like it invoked a v1 planner even though v1 and v2 share the same frozen local execution representation. This removes that migration-era coupling while preserving the public minor-version compatibility contract. ## How The shared builder returns neutral internal execution-plan fields. The v1 wrapper maps those fields back to the existing `PlanResult`; the v2 publisher consumes them directly. Compatibility is intentional and covered by exact shape tests: - omitted `planProtocol` still serializes/selects `"v1"`; - v1 layouts, descriptor-less decoding, event unions, workflow branches, and exports remain; - the v1 descriptor JSON is byte-identical and `CURRENT_PLAN_PROTOCOL` is an identity-preserving alias; - v2 manifest bytes, key order, hash framing, and `sourcePlanV1Hash` wire key remain unchanged; - no enumerable neutral hash field was added to manifests or returned result objects; - v1/v2 result objects, cloud event payloads, and SDK handle key sets remain unchanged. ## Test plan - Focused Plan v1/v2/protocol/export/size compatibility: 141 passed - `@hyperframes/core`: 1,419 passed - `@hyperframes/producer` unit lane: 990 passed - `@hyperframes/aws-lambda`: 140 passed - `@hyperframes/gcp-cloud-run`: 101 passed - Producer, Lambda, and Cloud Run typechecks - Repository-wide lint, format check, workspace/package-subpath checks - Full workspace build - `git diff --check` - [x] Unit tests added/updated - [ ] Manual testing performed - [x] Documentation updated (if applicable)
329 lines
10 KiB
TypeScript
329 lines
10 KiB
TypeScript
// These protocol rejection cases intentionally repeat the arrange/assert shape
|
|
// so each malformed wire descriptor remains independently readable.
|
|
// fallow-ignore-file code-duplication
|
|
|
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { assemble } from "./assemble.js";
|
|
import {
|
|
CURRENT_PLAN_PROTOCOL,
|
|
DISTRIBUTED_RENDER_CAPABILITIES,
|
|
getDistributedRenderCapabilities,
|
|
PLAN_ARTIFACT_LAYOUT,
|
|
PLAN_HASH_SCHEMA,
|
|
PLAN_PROTOCOL_V1,
|
|
PLAN_PROTOCOL_V2,
|
|
PLAN_PROTOCOL_UNSUPPORTED,
|
|
PLAN_SCHEMA_VERSION,
|
|
PlanProtocolUnsupportedError,
|
|
readPlanProtocol,
|
|
type DistributedRenderCapabilities,
|
|
type PlanProtocolConsumerCapabilities,
|
|
type PlanProtocolDescriptor,
|
|
} from "./planProtocol.js";
|
|
import {
|
|
CHUNK_INDEX_OUT_OF_RANGE,
|
|
renderChunk,
|
|
RenderChunkValidationError,
|
|
} from "./renderChunk.js";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function expectUnsupported(run: () => unknown): PlanProtocolUnsupportedError {
|
|
let caught: unknown;
|
|
try {
|
|
run();
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
expect(caught).toBeInstanceOf(PlanProtocolUnsupportedError);
|
|
expect((caught as PlanProtocolUnsupportedError).code).toBe(PLAN_PROTOCOL_UNSUPPORTED);
|
|
return caught as PlanProtocolUnsupportedError;
|
|
}
|
|
|
|
function createReaderPlan(options: {
|
|
protocol?: unknown;
|
|
includeProtocol?: boolean;
|
|
malformedDownstreamArtifacts?: boolean;
|
|
omitDownstreamArtifacts?: boolean;
|
|
}): string {
|
|
const planDir = mkdtempSync(join(tmpdir(), "hf-plan-protocol-"));
|
|
tempDirs.push(planDir);
|
|
|
|
const planJson: Record<string, unknown> = {
|
|
planHash: "fake",
|
|
totalFrames: 1,
|
|
hasAudio: false,
|
|
dimensions: {
|
|
fpsNum: 30,
|
|
fpsDen: 1,
|
|
width: 16,
|
|
height: 16,
|
|
format: "png-sequence",
|
|
},
|
|
};
|
|
if (options.includeProtocol === true) {
|
|
planJson.protocol = options.protocol;
|
|
}
|
|
|
|
writeFileSync(join(planDir, "plan.json"), JSON.stringify(planJson), "utf-8");
|
|
if (options.omitDownstreamArtifacts === true) {
|
|
return planDir;
|
|
}
|
|
|
|
mkdirSync(join(planDir, "meta"), { recursive: true });
|
|
writeFileSync(
|
|
join(planDir, "meta", "encoder.json"),
|
|
options.malformedDownstreamArtifacts ? "{not-json" : "{}",
|
|
"utf-8",
|
|
);
|
|
writeFileSync(
|
|
join(planDir, "meta", "chunks.json"),
|
|
options.malformedDownstreamArtifacts
|
|
? "{not-json"
|
|
: JSON.stringify([{ index: 0, startFrame: 0, endFrame: 1 }]),
|
|
"utf-8",
|
|
);
|
|
return planDir;
|
|
}
|
|
|
|
describe("readPlanProtocol()", () => {
|
|
it("keeps the v1 descriptor byte-for-byte and identity-compatible", () => {
|
|
expect(PLAN_PROTOCOL_V1).toBe(CURRENT_PLAN_PROTOCOL);
|
|
expect(JSON.stringify(PLAN_PROTOCOL_V1)).toBe(
|
|
'{"schemaVersion":1,"artifactLayout":"plan-dir-v1","hashSchema":"hyperframes-plan-hash-v1"}',
|
|
);
|
|
});
|
|
|
|
it("treats an absent descriptor as legacy v1", () => {
|
|
expect(readPlanProtocol({ planHash: "legacy" })).toBe(CURRENT_PLAN_PROTOCOL);
|
|
});
|
|
|
|
it("enforces whether a worker accepts descriptor-less legacy v1 plans", () => {
|
|
const capabilities: PlanProtocolConsumerCapabilities = {
|
|
accepts: [CURRENT_PLAN_PROTOCOL],
|
|
acceptsLegacyV1WithoutDescriptor: false,
|
|
};
|
|
|
|
expectUnsupported(() => readPlanProtocol({ planHash: "legacy" }, capabilities));
|
|
});
|
|
|
|
it("enforces the worker's accepted protocol set", () => {
|
|
const capabilities: PlanProtocolConsumerCapabilities = {
|
|
accepts: [],
|
|
acceptsLegacyV1WithoutDescriptor: true,
|
|
};
|
|
|
|
expectUnsupported(() => readPlanProtocol({ protocol: CURRENT_PLAN_PROTOCOL }, capabilities));
|
|
expectUnsupported(() => readPlanProtocol({ planHash: "legacy" }, capabilities));
|
|
});
|
|
|
|
it("accepts the known v1 descriptor and ignores unknown optional fields", () => {
|
|
expect(
|
|
readPlanProtocol({
|
|
protocol: {
|
|
schemaVersion: PLAN_SCHEMA_VERSION,
|
|
artifactLayout: PLAN_ARTIFACT_LAYOUT,
|
|
hashSchema: PLAN_HASH_SCHEMA,
|
|
producerBuildId: "optional-future-metadata",
|
|
},
|
|
}),
|
|
).toBe(CURRENT_PLAN_PROTOCOL);
|
|
});
|
|
|
|
it("accepts the explicit v2 descriptor", () => {
|
|
expect(readPlanProtocol({ protocol: PLAN_PROTOCOL_V2 })).toBe(PLAN_PROTOCOL_V2);
|
|
});
|
|
|
|
it("rejects malformed and partial descriptors", () => {
|
|
for (const protocol of [
|
|
null,
|
|
[],
|
|
"v1",
|
|
{},
|
|
{ schemaVersion: PLAN_SCHEMA_VERSION },
|
|
{
|
|
schemaVersion: PLAN_SCHEMA_VERSION,
|
|
artifactLayout: PLAN_ARTIFACT_LAYOUT,
|
|
},
|
|
]) {
|
|
expectUnsupported(() => readPlanProtocol({ protocol }));
|
|
}
|
|
});
|
|
|
|
it("rejects unknown schema, layout, and hash-schema values", () => {
|
|
for (const protocol of [
|
|
{ ...CURRENT_PLAN_PROTOCOL, schemaVersion: 2 },
|
|
{ ...CURRENT_PLAN_PROTOCOL, artifactLayout: "plan-dir-v2" },
|
|
{ ...CURRENT_PLAN_PROTOCOL, hashSchema: "hyperframes-plan-hash-v2" },
|
|
]) {
|
|
expectUnsupported(() => readPlanProtocol({ protocol }));
|
|
}
|
|
});
|
|
|
|
it("keeps unsupported-protocol messages bounded and non-reflective", () => {
|
|
const untrustedValue = "secret-".repeat(1_000);
|
|
const error = expectUnsupported(() =>
|
|
readPlanProtocol({
|
|
protocol: { ...CURRENT_PLAN_PROTOCOL, hashSchema: untrustedValue },
|
|
}),
|
|
);
|
|
|
|
expect(error.message).not.toContain("secret-");
|
|
expect(error.message.length).toBeLessThan(200);
|
|
});
|
|
});
|
|
|
|
describe("getDistributedRenderCapabilities()", () => {
|
|
it("reports explicit v1 and v2 support for every distributed role", () => {
|
|
expect(getDistributedRenderCapabilities()).toBe(DISTRIBUTED_RENDER_CAPABILITIES);
|
|
expect(DISTRIBUTED_RENDER_CAPABILITIES).toEqual({
|
|
roles: {
|
|
planner: {
|
|
produces: [CURRENT_PLAN_PROTOCOL, PLAN_PROTOCOL_V2],
|
|
},
|
|
chunk: {
|
|
accepts: [CURRENT_PLAN_PROTOCOL, PLAN_PROTOCOL_V2],
|
|
acceptsLegacyV1WithoutDescriptor: true,
|
|
},
|
|
assembler: {
|
|
accepts: [CURRENT_PLAN_PROTOCOL, PLAN_PROTOCOL_V2],
|
|
acceptsLegacyV1WithoutDescriptor: true,
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("can express a v2 planner with dual-version readers", () => {
|
|
const futureV2: PlanProtocolDescriptor = {
|
|
schemaVersion: 2,
|
|
artifactLayout: "plan-dir-v2",
|
|
hashSchema: "hyperframes-plan-hash-v2",
|
|
};
|
|
const rolloutCapabilities: DistributedRenderCapabilities = {
|
|
roles: {
|
|
planner: {
|
|
produces: [futureV2],
|
|
},
|
|
chunk: {
|
|
accepts: [CURRENT_PLAN_PROTOCOL, futureV2],
|
|
acceptsLegacyV1WithoutDescriptor: true,
|
|
},
|
|
assembler: {
|
|
accepts: [CURRENT_PLAN_PROTOCOL, futureV2],
|
|
acceptsLegacyV1WithoutDescriptor: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(rolloutCapabilities.roles.planner.produces).toEqual([futureV2]);
|
|
expect(rolloutCapabilities.roles.chunk.accepts).toEqual([CURRENT_PLAN_PROTOCOL, futureV2]);
|
|
expect(rolloutCapabilities.roles.assembler.accepts).toEqual([CURRENT_PLAN_PROTOCOL, futureV2]);
|
|
});
|
|
});
|
|
|
|
describe("distributed plan protocol readers", () => {
|
|
it("renderChunk accepts a legacy plan without a descriptor", async () => {
|
|
const planDir = createReaderPlan({});
|
|
|
|
let caught: unknown;
|
|
try {
|
|
await renderChunk(planDir, 999, join(planDir, "unused-output"));
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(RenderChunkValidationError);
|
|
expect((caught as RenderChunkValidationError).code).toBe(CHUNK_INDEX_OUT_OF_RANGE);
|
|
});
|
|
|
|
it("assemble accepts a legacy plan without a descriptor", async () => {
|
|
const planDir = createReaderPlan({});
|
|
const missingChunk = join(planDir, "missing-chunk");
|
|
|
|
await expect(
|
|
assemble(planDir, [missingChunk], null, join(planDir, "unused-output")),
|
|
).rejects.toThrow("chunk path does not exist");
|
|
});
|
|
|
|
it("renderChunk rejects an unknown v2 protocol before requiring v1 artifacts", async () => {
|
|
const planDir = createReaderPlan({
|
|
includeProtocol: true,
|
|
protocol: { ...CURRENT_PLAN_PROTOCOL, schemaVersion: 2 },
|
|
omitDownstreamArtifacts: true,
|
|
});
|
|
|
|
let caught: unknown;
|
|
try {
|
|
await renderChunk(planDir, 0, join(planDir, "unused-output"));
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(PlanProtocolUnsupportedError);
|
|
expect((caught as PlanProtocolUnsupportedError).code).toBe(PLAN_PROTOCOL_UNSUPPORTED);
|
|
});
|
|
|
|
it("assemble rejects an unknown v2 protocol before requiring v1 artifacts", async () => {
|
|
const planDir = createReaderPlan({
|
|
includeProtocol: true,
|
|
protocol: { ...CURRENT_PLAN_PROTOCOL, schemaVersion: 2 },
|
|
omitDownstreamArtifacts: true,
|
|
});
|
|
|
|
let caught: unknown;
|
|
try {
|
|
await assemble(planDir, [], null, join(planDir, "unused-output"));
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(PlanProtocolUnsupportedError);
|
|
expect((caught as PlanProtocolUnsupportedError).code).toBe(PLAN_PROTOCOL_UNSUPPORTED);
|
|
});
|
|
|
|
it("legacy activities reject a recognized v2 root before v1 layout access", async () => {
|
|
const planDir = createReaderPlan({
|
|
includeProtocol: true,
|
|
protocol: PLAN_PROTOCOL_V2,
|
|
omitDownstreamArtifacts: true,
|
|
});
|
|
|
|
await expect(renderChunk(planDir, 0, join(planDir, "unused-output"))).rejects.toThrow(
|
|
"must be materialized before v1 layout access",
|
|
);
|
|
await expect(assemble(planDir, [], null, join(planDir, "unused-output"))).rejects.toThrow(
|
|
"must be materialized before v1 layout access",
|
|
);
|
|
});
|
|
|
|
it("assemble rejects a partial protocol before parsing chunks", async () => {
|
|
const planDir = createReaderPlan({
|
|
includeProtocol: true,
|
|
protocol: {
|
|
schemaVersion: PLAN_SCHEMA_VERSION,
|
|
artifactLayout: PLAN_ARTIFACT_LAYOUT,
|
|
},
|
|
malformedDownstreamArtifacts: true,
|
|
});
|
|
|
|
let caught: unknown;
|
|
try {
|
|
await assemble(planDir, [], null, join(planDir, "unused-output"));
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(PlanProtocolUnsupportedError);
|
|
expect((caught as PlanProtocolUnsupportedError).code).toBe(PLAN_PROTOCOL_UNSUPPORTED);
|
|
});
|
|
});
|