feat: workflow graph system — blocks 1-20 complete checkpoint
Full graph-based workflow execution engine with: - WorkflowGraphRuntime with two-phase dispatch (collect → fire Celery tasks) - Node registry with contract validation, socket types, execution kinds - WorkflowRuntimeServices: preflight, context resolution, shadow-mode A/B - WorkflowRouter: CRUD + dispatch/preflight/run-history API endpoints - OutputTypeContracts: workflow binding, rollout-mode resolution - Admin router: output-type workflow binding endpoints - Frontend: complete workflow editor with drag-drop canvas, node inspector, module bundles, reference bundles, preflight panel, validation banner, authoring guidance, blueprint templates, shadow/rollout gate UI - Tests: comprehensive coverage for all workflow modules - Docs: NODE_CONTRACT_AUDIT and VALIDATION_ERROR_INVENTORY All 20 blocks from NEXT_20_BLOCK_BATCH_PLAN completed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,25 @@ const definitions: WorkflowNodeDefinition[] = [
|
||||
artifact_roles_consumed: [],
|
||||
legacy_source: 'legacy.notify',
|
||||
},
|
||||
{
|
||||
step: 'export_blend',
|
||||
label: 'Export Blend',
|
||||
family: 'order_line',
|
||||
module_key: 'media.export_blend',
|
||||
category: 'output',
|
||||
description: 'Export blend.',
|
||||
node_type: 'outputNode',
|
||||
icon: 'download',
|
||||
defaults: {},
|
||||
fields: [],
|
||||
execution_kind: 'bridge',
|
||||
legacy_compatible: true,
|
||||
input_contract: { context: 'order_line', requires: ['render_template'] },
|
||||
output_contract: { context: 'order_line', provides: ['blend_asset'] },
|
||||
artifact_roles_produced: [],
|
||||
artifact_roles_consumed: [],
|
||||
legacy_source: 'legacy.export_blend',
|
||||
},
|
||||
]
|
||||
|
||||
const cadDefinitions: WorkflowNodeDefinition[] = [
|
||||
@@ -302,12 +321,18 @@ describe('workflowModuleBundles', () => {
|
||||
test('exposes family-scoped bundles when required steps exist', () => {
|
||||
const bundles = getWorkflowModuleBundles(definitions, 'order_line')
|
||||
|
||||
expect(bundles.map(bundle => bundle.id)).toEqual(['still_render_core', 'output_publish_notify'])
|
||||
expect(bundles.map(bundle => bundle.id)).toEqual([
|
||||
'scene_prep_core',
|
||||
'materials_core',
|
||||
'still_render_core',
|
||||
'output_publish_notify',
|
||||
'blend_export_publish',
|
||||
])
|
||||
})
|
||||
|
||||
test('creates a connected bundle insertion graph for still-render authoring', () => {
|
||||
test('creates a connected bundle insertion graph for scene-prep authoring', () => {
|
||||
const insertion = createWorkflowModuleBundleInsertion({
|
||||
bundleId: 'still_render_core',
|
||||
bundleId: 'scene_prep_core',
|
||||
graphFamily: 'order_line',
|
||||
nodeDefinitionsByStep: Object.fromEntries(definitions.map(definition => [definition.step, definition])),
|
||||
existingNodes: [],
|
||||
@@ -317,22 +342,44 @@ describe('workflowModuleBundles', () => {
|
||||
expect(insertion.ok).toBe(true)
|
||||
if (!insertion.ok) return
|
||||
|
||||
expect(insertion.nodes).toHaveLength(6)
|
||||
expect(insertion.edges).toHaveLength(5)
|
||||
expect(insertion.nodes).toHaveLength(3)
|
||||
expect(insertion.edges).toHaveLength(2)
|
||||
expect(insertion.nodes[0].position).toEqual({ x: 200, y: 320 })
|
||||
expect(insertion.nodes[1].position).toEqual({ x: 420, y: 320 })
|
||||
expect(insertion.nodes[0].data).toMatchObject({ step: 'order_line_setup', label: 'Order Line Setup' })
|
||||
expect(insertion.nodes[5].data).toMatchObject({ step: 'blender_still', label: 'Blender Still' })
|
||||
expect(insertion.nodes[2].data).toMatchObject({ step: 'glb_bbox', label: 'Compute Bounding Box' })
|
||||
expect(insertion.edges[0]).toMatchObject({
|
||||
source: insertion.nodes[0].id,
|
||||
target: insertion.nodes[1].id,
|
||||
})
|
||||
})
|
||||
|
||||
test('creates a single-node still-render module for stage-level insertion', () => {
|
||||
const insertion = createWorkflowModuleBundleInsertion({
|
||||
bundleId: 'still_render_core',
|
||||
graphFamily: 'order_line',
|
||||
nodeDefinitionsByStep: Object.fromEntries(definitions.map(definition => [definition.step, definition])),
|
||||
existingNodes: [],
|
||||
preferredPosition: { x: 640, y: 180 },
|
||||
})
|
||||
|
||||
expect(insertion.ok).toBe(true)
|
||||
if (!insertion.ok) return
|
||||
|
||||
expect(insertion.nodes).toHaveLength(1)
|
||||
expect(insertion.edges).toHaveLength(0)
|
||||
expect(insertion.nodes[0].position).toEqual({ x: 640, y: 180 })
|
||||
expect(insertion.nodes[0].data).toMatchObject({ step: 'blender_still', label: 'Blender Still' })
|
||||
})
|
||||
|
||||
test('exposes full reference paths for complete non-legacy authoring flows', () => {
|
||||
const bundles = getWorkflowReferenceBundles(definitions, 'order_line')
|
||||
|
||||
expect(bundles.map(bundle => bundle.id)).toEqual(['still_render_reference'])
|
||||
expect(bundles.map(bundle => bundle.id)).toEqual([
|
||||
'still_render_reference',
|
||||
'still_render_alpha_reference',
|
||||
'still_render_blend_reference',
|
||||
])
|
||||
})
|
||||
|
||||
test('creates the canonical still-render reference graph with branched edges', () => {
|
||||
@@ -354,7 +401,13 @@ describe('workflowModuleBundles', () => {
|
||||
expect(insertion.nodes[5].data).toMatchObject({
|
||||
step: 'blender_still',
|
||||
label: 'Still Render',
|
||||
params: { use_custom_render_settings: true },
|
||||
params: {
|
||||
use_custom_render_settings: false,
|
||||
render_engine: 'cycles',
|
||||
samples: 256,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
})
|
||||
expect(insertion.edges).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -386,12 +439,13 @@ describe('workflowModuleBundles', () => {
|
||||
expect(insertion.ok).toBe(true)
|
||||
if (!insertion.ok) return
|
||||
|
||||
expect(insertion.nodes).toHaveLength(8)
|
||||
expect(insertion.edges).toHaveLength(7)
|
||||
expect(insertion.nodes).toHaveLength(9)
|
||||
expect(insertion.edges).toHaveLength(9)
|
||||
expect(insertion.nodes.map(node => node.data.step)).toEqual([
|
||||
'resolve_step_path',
|
||||
'occ_object_extract',
|
||||
'occ_glb_export',
|
||||
'glb_bbox',
|
||||
'stl_cache_generate',
|
||||
'blender_render',
|
||||
'threejs_render',
|
||||
@@ -402,11 +456,15 @@ describe('workflowModuleBundles', () => {
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
source: insertion.nodes[2].id,
|
||||
target: insertion.nodes[4].id,
|
||||
target: insertion.nodes[5].id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
source: insertion.nodes[2].id,
|
||||
target: insertion.nodes[5].id,
|
||||
target: insertion.nodes[6].id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
source: insertion.nodes[3].id,
|
||||
target: insertion.nodes[6].id,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user