Validate a workflow definition and return a list of error messages. An empty list means the workflow is valid.
(definition: WorkflowDefinition)
| 122 | |
| 123 | |
| 124 | def validate_workflow(definition: WorkflowDefinition) -> list[str]: |
| 125 | """Validate a workflow definition and return a list of error messages. |
| 126 | |
| 127 | An empty list means the workflow is valid. |
| 128 | """ |
| 129 | errors: list[str] = [] |
| 130 | |
| 131 | # -- Schema version --------------------------------------------------- |
| 132 | if definition.schema_version not in ("1.0", "1"): |
| 133 | errors.append( |
| 134 | f"Unsupported schema_version {definition.schema_version!r}. " |
| 135 | f"Expected '1.0'." |
| 136 | ) |
| 137 | |
| 138 | # -- Top-level fields ------------------------------------------------- |
| 139 | if not definition.id: |
| 140 | errors.append("Workflow is missing 'workflow.id'.") |
| 141 | elif not _ID_PATTERN.match(definition.id): |
| 142 | errors.append( |
| 143 | f"Workflow ID {definition.id!r} must be lowercase alphanumeric " |
| 144 | f"with hyphens." |
| 145 | ) |
| 146 | |
| 147 | if not definition.name: |
| 148 | errors.append("Workflow is missing 'workflow.name'.") |
| 149 | |
| 150 | if not definition.version: |
| 151 | errors.append("Workflow is missing 'workflow.version'.") |
| 152 | elif not re.match(r"^\d+\.\d+\.\d+$", definition.version): |
| 153 | errors.append( |
| 154 | f"Workflow version {definition.version!r} is not valid " |
| 155 | f"semantic versioning (expected X.Y.Z)." |
| 156 | ) |
| 157 | |
| 158 | # -- Inputs ----------------------------------------------------------- |
| 159 | if not isinstance(definition.inputs, dict): |
| 160 | errors.append("'inputs' must be a mapping (or omitted).") |
| 161 | else: |
| 162 | for input_name, input_def in definition.inputs.items(): |
| 163 | if not isinstance(input_def, dict): |
| 164 | errors.append(f"Input {input_name!r} must be a mapping.") |
| 165 | continue |
| 166 | input_type = input_def.get("type") |
| 167 | if input_type and input_type not in ("string", "number", "boolean"): |
| 168 | errors.append( |
| 169 | f"Input {input_name!r} has invalid type {input_type!r}. " |
| 170 | f"Must be 'string', 'number', or 'boolean'." |
| 171 | ) |
| 172 | |
| 173 | # Validate the default eagerly so authoring mistakes (e.g. a |
| 174 | # default not in the declared enum, or a non-numeric default for |
| 175 | # a number input) surface at install/validation time instead of |
| 176 | # at workflow-execution time. ``"auto"`` for the integration |
| 177 | # input is a runtime-resolved sentinel, so only the |
| 178 | # enum-membership check is exempted for that exact case — the |
| 179 | # declared type is still enforced (e.g. ``type: number`` paired |
| 180 | # with ``default: "auto"`` is still rejected). |
| 181 | if "default" in input_def: |