Compare commits

...

4 Commits

Author SHA1 Message Date
dante01yoon
9dd5bbb025 fix(sharing): match regular load policy on shared workflow validation
Switch to raw workflow_json fallback when Zod validation fails,
matching the policy at scripts/app.ts:1191-1198. Share service no
longer validates directly — loadGraphData applies the same fallback
under Comfy.Validation.Workflows, removing the cross-path policy
inconsistency.

Drops the tolerantArray combinator and per-field linearData.inputs
relaxation added earlier in this PR. Zod schemas stay strict as the
canonical spec; tolerance lives at the consumer boundary.

Fixes the reported share=21e32125c692 load failure and any future
extra.* shape drift, not just linearData.inputs.
2026-05-14 11:19:35 +09:00
dante01yoon
db969dd50e refactor(validation): move linearData.inputs tolerance to tolerantArray combinator
Address review feedback: schema should represent the *specified* shape,
not "what will work and what won't". The previous in-schema transform
mixed canonical spec with tolerance, making the schema misleading to read.

- Restore `zLinearInput` to its strict union (2-tuple OR 3-tuple with
  `{ height? }`) — this is the canonical write contract.
- Introduce `tolerantArray(itemSchema, label)` combinator that drops
  invalid entries with a console warning, leaving valid ones intact.
  The item schema stays strict; tolerance lives at the array container.
- Apply `tolerantArray` only to `extra.linearData.inputs` for now.
  Other `extra.*` arrays remain untouched until proven necessary.
- Document that execution-critical arrays (`nodes`, `links`,
  `widget_values`) must keep `z.array` so malformed entries fail loudly.

Behavior change vs the previous commit: legacy `[id, name, <number>]`
shapes are no longer coerced to `[id, name, { height: <number> }]` —
they're dropped. Without seeing the actual production data, coercing
guesses are riskier than dropping; a dedicated migration can be added
later if a recognizable legacy shape is confirmed.
2026-05-13 13:14:32 +09:00
dante01yoon
4c20d44e6b docs(validation): convert zLinearInput comment to JSDoc 2026-05-13 12:46:46 +09:00
dante01yoon
a3e8f8625d fix(validation): relax linearData.inputs schema to load legacy shares
Strict `z.union([3-tuple, 2-tuple])` rejected the entire workflow when
any single `extra.linearData.inputs` entry didn't match, producing
"Failed to load shared workflow: invalid workflow data" for published
shares whose entries were stored with legacy or unknown 3rd-element
shapes (raw height number, forward-compat fields, etc).

The cloud accepts and serves `workflow_json` as an opaque
`Record<string, unknown>` (see `packages/ingest-types/src/zod.gen.ts`),
so non-conforming shapes do get persisted and surface only on the
frontend's read path. Until the SOT gap is closed, validation here
must be tolerant of historical variants.

Replace the union with a permissive `tuple([nodeId, string]).rest(unknown)`
that normalizes the trailing element:
- `[id, name]` → unchanged
- `[id, name, { height? }]` → unchanged
- `[id, name, <number>]` → coerced to `[id, name, { height: <number> }]`
- `[id, name, <other>]` → trailing dropped, kept as 2-tuple
- `[id, name, _, _, ...]` → extras dropped

Only entries missing the `[nodeId, string]` prefix still reject.
2026-05-13 12:29:28 +09:00
2 changed files with 30 additions and 15 deletions

View File

@@ -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 }))

View File

@@ -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
}