fix: Remove duplicate app workflow validation (#12208)

## Summary

`extra.linearData.inputs` validation rejected the entire workflow
whenever a single entry didn't match the strict `z.union([3-tuple,
2-tuple])`, surfacing as `Failed to load shared workflow: invalid
workflow data` for some published cloud shares (e.g.
`share=21e32125c692`).

## Changes

- **What**: Shared workflow load now matches the regular load policy at
`scripts/app.ts:1191-1198` — if Zod validation fails, fall back to the
raw `workflow_json` instead of throwing. Share service no longer runs
schema validation directly; `app.loadGraphData()` continues to validate
and apply the same raw fallback under `Comfy.Validation.Workflows`.
- **Breaking**: None — workflows that previously failed to load through
the share path now load with the same permissive behavior as workflows
opened through any other entry point.

## Review Focus

The original approach added a `tolerantArray` combinator to drop bad
`linearData.inputs` entries inside the schema. After review, the cleaner
direction is to keep schemas strict as the canonical spec and apply
tolerance at the consumer boundary — which `scripts/app.ts:1191-1198`
already does for the regular load path (`graphData = validatedGraphData
?? graphData` with the comment "Ideally we should not block users from
loading the workflow"). This PR aligns the share path with that existing
policy and removes a cross-path inconsistency rather than introducing a
new schema-level concept.

Consequences:
- Schemas in `workflowSchema.ts` stay unchanged (canonical spec).
- The `extra.*` shape drift problem is now solved generally, not just
for `linearData.inputs` — future fields hitting the same class of issue
will load instead of blocking.
- `validateComfyWorkflow` still logs the Zod error via `console.warn`
for debugging.

## Tests

- Existing schema tests stay strict (3-tuple, 2-tuple unions reject bad
shapes).
- New regression test in `workflowShareService.test.ts`: a
`workflow_json` that passes the share response envelope
(`record<unknown>`) but fails `ComfyWorkflowJSON` is returned raw, not
thrown.

### current prod

<img width="1144" height="565" alt="Screenshot 2026-05-13 at 1 29 00 PM"
src="https://github.com/user-attachments/assets/b1abf45b-a588-4ef5-a9ec-d14bd1096b6d"
/>

### test
`/?share=21e32125c692`

<img width="765" height="826" alt="Screenshot 2026-05-13 at 1 27 40 PM"
src="https://github.com/user-attachments/assets/96a3c405-e5fe-4732-9047-fed90768e6f6"
/>

## Follow-up

Structural issue separately: cloud OpenAPI defines `workflow_json` as
opaque `z.record(z.unknown())`
(`packages/ingest-types/src/zod.gen.ts:137,325,375,397,457`), so ingest
schema CI cannot enforce inner shape. Matt Miller is driving the
lift-schema-to-core-OpenAPI direction; tracked separately, not in this
PR.

Fixes FE-690.
This commit is contained in:
Dante
2026-05-30 14:20:50 +09:00
committed by github-actions[bot]
parent a6699f6922
commit e722edec2f
2 changed files with 30 additions and 15 deletions

View File

@@ -16,14 +16,6 @@ const mockGetShareableAssets = vi.fn()
const mockFetchApi = vi.fn()
const mockInvalidateInputAssetsIncludingPublic = vi.hoisted(() => 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),
@@ -408,6 +400,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 }))

View File

@@ -9,7 +9,6 @@ import type {
import { assetService } from '@/platform/assets/services/assetService'
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,
@@ -249,12 +248,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
}