fix: harden composite input schema validation

This commit is contained in:
lda
2026-08-13 17:38:39 +07:00 Verified
parent fa22a1a6f7
commit 4120174a09
5 changed files with 631 additions and 82 deletions
+80 -14
View File
@@ -65,9 +65,9 @@ from .operation_context import WorkflowOperationContext
from .schema_projection import (
project_output_property_to_state_schema,
project_schema_path_to_schema_path,
schema_fragment_at_path,
schema_path_exists,
validate_json_value_at_schema_path,
schema_fragment_at_location,
schema_location_is_explicit,
validate_json_value_at_schema_location,
)
_PROJECT_DRAFT_WORKSPACE = JsonProjector(DraftWorkspaceResult)
@@ -618,11 +618,20 @@ class WorkflowDraftAuthoringApi:
for index, binding in enumerate(bindings):
target_parts = binding.target.parts
try:
schema_fragment_at_path(
schema_fragment_at_location(
capability_schema,
target_parts,
label="capability input schema",
)
if not schema_location_is_explicit(
capability_schema,
target_parts,
label="capability input schema",
):
raise ValueError(
f"capability input schema path {'.'.join(target_parts)!r} "
"is not declared"
)
except ValueError as exc:
raise ValueError(
f"bindings[{index}].target {str(binding.target)!r} "
@@ -647,9 +656,9 @@ class WorkflowDraftAuthoringApi:
raise ValueError(
f"bindings[{index}].value for target '.' must be a JSON object"
)
validate_json_value_at_schema_path(
validate_json_value_at_schema_location(
schema=capability_schema,
parts=target_parts,
location=target_parts,
value=binding.value,
label=f"bindings[{index}].value",
)
@@ -662,13 +671,18 @@ class WorkflowDraftAuthoringApi:
target_schema = (
projected_input if source.root == "input" else projected_state
)
if not schema_path_exists(target_schema, source.parts):
if not schema_location_is_explicit(
target_schema,
source.parts,
label=f"{source.root} source schema",
):
target_schema = project_schema_path_to_schema_path(
target_schema=target_schema,
source_schema=capability_schema,
source_parts=target_parts,
target_parts=source.parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
if source.root == "input":
projected_input = target_schema
@@ -802,11 +816,20 @@ class WorkflowDraftAuthoringApi:
continue
try:
source_schemas[index] = source_schema
source_fragments[index] = schema_fragment_at_path(
source_fragments[index] = schema_fragment_at_location(
source_schema,
binding.path.parts,
label=f"workflow {binding.path.root} schema",
)
if not schema_location_is_explicit(
source_schema,
binding.path.parts,
label=f"workflow {binding.path.root} schema",
):
raise ValueError(
f"workflow {binding.path.root} schema path "
f"{'.'.join(binding.path.parts)!r} is not declared"
)
except ValueError as exc:
raise ValueError(
f"bindings[{index}].path {str(binding.path)!r} "
@@ -827,7 +850,11 @@ class WorkflowDraftAuthoringApi:
)
source_schema = source_schemas.get(index)
if source_schema is None:
if not schema_path_exists(projected, target_parts):
if not schema_location_is_explicit(
projected,
target_parts,
label="workflow output schema",
):
raise ValueError(
f"bindings[{index}].path {str(binding.path)!r} "
"requires a declared output target"
@@ -851,6 +878,7 @@ class WorkflowDraftAuthoringApi:
source_parts=binding.path.parts,
target_parts=target_parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
except ValueError as exc:
raise ValueError(
@@ -865,13 +893,17 @@ class WorkflowDraftAuthoringApi:
raise ValueError(
f"bindings[{index}].value for root target must be an object"
)
if not schema_path_exists(projected, target_parts):
if not schema_location_is_explicit(
projected,
target_parts,
label="workflow output schema",
):
raise ValueError(
f"bindings[{index}].target {str(binding.target)!r} is not declared"
)
validate_json_value_at_schema_path(
validate_json_value_at_schema_location(
schema=projected,
parts=target_parts,
location=target_parts,
value=binding.value,
label=f"bindings[{index}].value",
schema_label="workflow output schema",
@@ -928,11 +960,20 @@ class WorkflowDraftAuthoringApi:
for index, binding in enumerate(bindings):
try:
schema_fragment_at_path(
schema_fragment_at_location(
capability_schema,
binding.source.parts,
label="capability output schema",
)
if not schema_location_is_explicit(
capability_schema,
binding.source.parts,
label="capability output schema",
):
raise ValueError(
f"capability output schema path "
f"{'.'.join(binding.source.parts)!r} is not declared"
)
except ValueError as exc:
raise ValueError(
f"bindings[{index}].source {str(binding.source)!r} "
@@ -954,6 +995,7 @@ class WorkflowDraftAuthoringApi:
source_parts=source_parts,
target_parts=target_parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
except ValueError as exc:
raise ValueError(
@@ -1033,7 +1075,11 @@ class WorkflowDraftAuthoringApi:
target_schema = workspace.draft.get(schema_key, {})
if not isinstance(target_schema, dict):
raise ValueError(f"draft {schema_key} must be an object")
if schema_path_exists(target_schema, source_parts):
if schema_location_is_explicit(
target_schema,
source_parts,
label=f"{source_root} source schema",
):
projected = target_schema
else:
projected = project_schema_path_to_schema_path(
@@ -1041,6 +1087,7 @@ class WorkflowDraftAuthoringApi:
source_schema=input_schema,
source_parts=target_parts,
target_parts=source_parts,
allow_additional_properties=True,
)
input_bindings = _upsert_input_path_binding(
step.get("input", []),
@@ -1068,6 +1115,14 @@ class WorkflowDraftAuthoringApi:
output_schema_source = (
spec.output_schema_contract or spec.output_model.model_json_schema()
)
if not schema_location_is_explicit(
output_schema_source,
source_parts,
label="output schema",
):
raise ValueError(
f"source schema path {'.'.join(source_parts)!r} is not declared"
)
state_path_str = format_toml_path_segments(("state", *target_parts))
output_target_str = format_toml_path_segments(target_parts)
@@ -1080,6 +1135,7 @@ class WorkflowDraftAuthoringApi:
source_parts=source_parts,
target_parts=target_parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
output_schema = workspace.draft.get("output_schema", {})
@@ -1091,6 +1147,7 @@ class WorkflowDraftAuthoringApi:
source_parts=source_parts,
target_parts=target_parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
step_output_bindings, previous_state_path = _upsert_step_output_binding(
@@ -1135,6 +1192,14 @@ class WorkflowDraftAuthoringApi:
output_schema = (
spec.output_schema_contract or spec.output_model.model_json_schema()
)
if not schema_location_is_explicit(
output_schema,
source_parts,
label="output schema",
):
raise ValueError(
f"source schema path {'.'.join(source_parts)!r} is not declared"
)
target_schema = workspace.draft.get("state_schema", {})
if not isinstance(target_schema, dict):
raise ValueError("draft state_schema must be an object")
@@ -1144,6 +1209,7 @@ class WorkflowDraftAuthoringApi:
source_parts=source_parts,
target_parts=target_parts,
allow_existing_equivalent=True,
allow_additional_properties=True,
)
output_bindings, _previous_state_path = _upsert_step_output_binding(
step.get("output", []),