# Schema Validation Boundary `wf_core` uses `SchemaRef` to carry JSON-schema-like shapes on workflow input, node input, node output, and workflow output. Runtime payload validation is delegated to the existing `jsonschema` library. ## Current Behavior `wf_core.runtime.ops.schemas.validate_payload_against_schema` currently: - converts `SchemaRef` into a JSON Schema dictionary - asks `jsonschema` to validate the schema itself - asks `jsonschema` to validate the payload - wraps validation failures in `WorkflowExecutionError` This means normal JSON Schema checks such as object shape, required fields, property types, nested required fields, arrays, and item types are enforced by the library. It still does not solve: - semantic compatibility between Pydantic-generated schemas and every possible external JSON Schema dialect - typed Python object creation from arbitrary JSON Schema - workflow state merge behavior beyond declared exact-path metadata - deep node-local map-path validation beyond statically knowable schema roots - better domain-specific error payloads beyond `WorkflowExecutionError` This means schema fields are contracts for authoring, planning, documentation, and mapping validation. Runtime state merge behavior is separate metadata on declared exact state paths; undeclared paths still use `replace`. Final workflow output has two projection modes: - If `Workflow.output` contains bindings, those bindings build the public output payload from graph paths such as `state.result.message`. - If `Workflow.output` is empty, legacy projection copies same-named top-level state keys listed in `workflow.output_schema.properties`. Prefer explicit `Workflow.output` bindings for new workflows. The same-named state projection exists for compatibility with older plans. The projected payload is then validated against `workflow.output_schema`. ## Why This Matters Node and workflow boundaries can now reject wrong primitive/container types when the schema declares them. This matters before workflows are generated by an LLM or backed by arbitrary MCP tools. ## Authoring Footguns ### Prefer explicit entry arrays at LLM-facing boundaries `dict[str, SomeModel]` is often pleasant in Python but weak as a human- or LLM-facing schema. It commonly appears as a generic object with arbitrary keys, and many clients communicate or render it much less clearly than an explicit list shape. Prefer: ```python class Entry(BaseModel): key: str value: SomeModel entries: list[Entry] ``` over: ```python entries: dict[str, SomeModel] ``` when the schema is meant for MCP tools, LLM planning, or durable workflow authoring contracts. Internal Python state can still use dictionaries when that is the right runtime shape. ### Do not assume all JSON Schema consumers handle references equally Pydantic-generated schemas may use `$defs` and local `$ref` references for nested models. The runtime delegates validation to `jsonschema`, which supports that structure, but display layers and downstream consumers may vary in how well they present or reason about referenced shapes. `wf_authoring` resolves the local Pydantic `$ref -> $defs` pattern only for its own state-field projection. That helper is not a general-purpose JSON Schema flattener, and arbitrary external schemas should not be assumed to share the same shape. ## Intended Seam The schema adapter lives behind: ```text wf_core.runtime.ops.schemas.validate_payload_against_schema ``` Callers should not choose or invoke the validation backend directly. The runtime keeps one small validation interface and hides the validation backend. ## Future Requirements - Validate workflow input, node input, node output, and final workflow output with the same semantics. - Return errors that name the failing boundary and path. - Keep schema validation separate from graph structure validation. - Keep `wf_authoring` free to generate schemas from Pydantic models without making the core runtime depend on authoring internals. - Add targeted tests as externally sourced schemas grow more complex. ## Non-Goals For Now - Do not add ad hoc type checks throughout runtime state operations. - Do not let each node wrapper invent separate validation behavior. - Do not conflate graph validation with payload validation. - Do not hand-write a general JSON Schema implementation.