diff --git a/src/platform/workflow/sharing/services/workflowShareService.test.ts b/src/platform/workflow/sharing/services/workflowShareService.test.ts index 67371d78fb..2888b2eb36 100644 --- a/src/platform/workflow/sharing/services/workflowShareService.test.ts +++ b/src/platform/workflow/sharing/services/workflowShareService.test.ts @@ -15,14 +15,6 @@ vi.mock('@/scripts/app', () => ({ const mockGetShareableAssets = vi.fn() const mockFetchApi = vi.fn() -vi.mock( - '@/platform/workflow/validation/schemas/workflowSchema', - async (importOriginal) => ({ - ...(await importOriginal()), - validateComfyWorkflow: vi.fn(async (json: unknown) => json) - }) -) - vi.mock('@/scripts/api', () => ({ api: { getShareableAssets: (...args: unknown[]) => mockGetShareableAssets(...args), @@ -369,6 +361,36 @@ describe(useWorkflowShareService, () => { ) }) + it('returns raw workflow_json when it does not match ComfyWorkflowJSON schema', async () => { + const rawWorkflowJson = { + extra: { + linearData: { + inputs: [ + [1, 'prompt'], + [2, 'seed', 'invalid-third-element'] + ], + outputs: [] + } + } + } + mockFetchApi.mockResolvedValue( + mockJsonResponse({ + share_id: 'share-raw', + workflow_id: 'wf-raw', + name: 'Raw', + listed: false, + publish_time: null, + workflow_json: rawWorkflowJson, + assets: [] + }) + ) + + const service = useWorkflowShareService() + const shared = await service.getSharedWorkflow('share-raw') + + expect(shared.workflowJson).toEqual(rawWorkflowJson) + }) + it('treats malformed publish-status payload as unpublished', async () => { mockFetchApi.mockResolvedValue(mockJsonResponse({ is_published: true })) diff --git a/src/platform/workflow/sharing/services/workflowShareService.ts b/src/platform/workflow/sharing/services/workflowShareService.ts index 284a860e19..73ff955b17 100644 --- a/src/platform/workflow/sharing/services/workflowShareService.ts +++ b/src/platform/workflow/sharing/services/workflowShareService.ts @@ -6,7 +6,6 @@ import type { } from '@/platform/workflow/sharing/types/shareTypes' import type { ThumbnailType } from '@/platform/workflow/sharing/types/comfyHubTypes' import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' -import { validateComfyWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema' import type { AssetInfo } from '@/schemas/apiSchema' import { zHubWorkflowPrefillResponse, @@ -246,12 +245,6 @@ export function useWorkflowShareService() { throw new Error('Failed to load shared workflow: invalid response') } - const validated = await validateComfyWorkflow(workflow.workflowJson) - if (!validated) { - throw new Error('Failed to load shared workflow: invalid workflow data') - } - workflow.workflowJson = validated - return workflow } diff --git a/src/platform/workflow/validation/schemas/workflowSchema.test.ts b/src/platform/workflow/validation/schemas/workflowSchema.test.ts index fe279219cf..1df45c09aa 100644 --- a/src/platform/workflow/validation/schemas/workflowSchema.test.ts +++ b/src/platform/workflow/validation/schemas/workflowSchema.test.ts @@ -1,5 +1,5 @@ import fs from 'fs' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { validateComfyWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema' import { defaultGraph } from '@/scripts/defaultGraph' @@ -111,56 +111,13 @@ describe('parseComfyWorkflow', () => { ]) }) - it('drops entries that do not match the strict union, keeps valid ones', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + it('rejects invalid config shape', async () => { const workflow = JSON.parse(JSON.stringify(defaultGraph)) workflow.extra = { - linearData: { - inputs: [ - [1, 'prompt'], - [2, 'seed', 'invalid-third-element'], - [3, 'cfg', { height: 100 }], - [4, 'steps', 240], - [5, 'sampler', { height: 80 }, 'future-field'], - [6] - ], - outputs: [] - } + linearData: { inputs: [[1, 'prompt', 'invalid']], outputs: [] } } const result = await validateComfyWorkflow(workflow) - expect(result).not.toBeNull() - expect(result!.extra!.linearData!.inputs).toEqual([ - [1, 'prompt'], - [3, 'cfg', { height: 100 }] - ]) - expect(warn).toHaveBeenCalledWith( - expect.stringContaining('extra.linearData.inputs') - ) - warn.mockRestore() - }) - - it('loads the workflow even when every linearData.inputs entry is invalid', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const workflow = JSON.parse(JSON.stringify(defaultGraph)) - workflow.extra = { - linearData: { inputs: [[1], 'garbage', null], outputs: [] } - } - const result = await validateComfyWorkflow(workflow) - expect(result).not.toBeNull() - expect(result!.extra!.linearData!.inputs).toEqual([]) - warn.mockRestore() - }) - - it('does not warn when every entry is valid', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const workflow = JSON.parse(JSON.stringify(defaultGraph)) - workflow.extra = { - linearData: { inputs: [[1, 'prompt']], outputs: [] } - } - const result = await validateComfyWorkflow(workflow) - expect(result).not.toBeNull() - expect(warn).not.toHaveBeenCalled() - warn.mockRestore() + expect(result).toBeNull() }) }) diff --git a/src/platform/workflow/validation/schemas/workflowSchema.ts b/src/platform/workflow/validation/schemas/workflowSchema.ts index 1c095b4983..1f1dc3fefc 100644 --- a/src/platform/workflow/validation/schemas/workflowSchema.ts +++ b/src/platform/workflow/validation/schemas/workflowSchema.ts @@ -282,44 +282,6 @@ const zConfig = z }) .passthrough() -const zLinearInputConfig = z - .object({ height: z.number().optional() }) - .passthrough() - -/** Canonical `linearData.inputs` entry shape. Strict — no tolerance here. */ -const zLinearInput = z.union([ - z.tuple([zNodeId, z.string(), zLinearInputConfig]), - z.tuple([zNodeId, z.string()]) -]) - -/** - * Array combinator that drops entries failing the item schema instead of - * rejecting the whole array. Item schema stays strict (spec); tolerance - * lives here at the container so the same policy applies uniformly to any - * `extra.*` array field without per-field consumer logic. - * - * Reserved for non-essential metadata in `extra.*`. Execution-critical - * arrays (`nodes`, `links`, `widget_values`) must use `z.array` directly so - * malformed entries fail loudly. - */ -function tolerantArray(itemSchema: T, label: string) { - return z.array(z.unknown()).transform((items): z.infer[] => { - const valid: z.infer[] = [] - let dropped = 0 - for (const item of items) { - const result = itemSchema.safeParse(item) - if (result.success) valid.push(result.data) - else dropped++ - } - if (dropped > 0) { - console.warn( - `[workflowSchema] dropped ${dropped} invalid entr${dropped === 1 ? 'y' : 'ies'} in ${label}` - ) - } - return valid - }) -} - const zExtra = z .object({ ds: zDS.optional(), @@ -332,10 +294,18 @@ const zExtra = z linearMode: z.boolean().optional(), linearData: z .object({ - inputs: tolerantArray( - zLinearInput, - 'extra.linearData.inputs' - ).optional(), + inputs: z + .array( + z.union([ + z.tuple([ + zNodeId, + z.string(), + z.object({ height: z.number().optional() }).passthrough() + ]), + z.tuple([zNodeId, z.string()]) + ]) + ) + .optional(), outputs: z.array(zNodeId).optional() }) .optional()