Skip to content

Commit 0dd3426

Browse files
fix(workflow): tighten auto-sentinel exemption to enum-only on #2421
Previously the ``integration: auto`` exemption skipped default validation entirely, which meant a workflow could declare an incompatible type (e.g. ``type: number`` with ``default: "auto"``) and still pass validation, only to fail later at runtime. Both the install-time check in ``validate_workflow()`` and the runtime coercion in ``_resolve_inputs()`` now exempt only the enum-membership check for the auto sentinel; the declared type is still enforced. Adds a regression test that asserts a ``type: number`` + ``default: "auto"`` workflow is rejected at validation time.
1 parent 67e4d1f commit 0dd3426

2 files changed

Lines changed: 61 additions & 11 deletions

File tree

src/specify_cli/workflows/engine.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,21 +148,30 @@ def validate_workflow(definition: WorkflowDefinition) -> list[str]:
148148
# default not in the declared enum, or a non-numeric default for
149149
# a number input) surface at install/validation time instead of
150150
# at workflow-execution time. ``"auto"`` for the integration
151-
# input is a runtime-resolved sentinel and is exempt.
151+
# input is a runtime-resolved sentinel, so only the
152+
# enum-membership check is exempted for that exact case — the
153+
# declared type is still enforced (e.g. ``type: number`` paired
154+
# with ``default: "auto"`` is still rejected).
152155
if "default" in input_def:
153156
default_value = input_def["default"]
154157
is_auto_integration = (
155158
input_name == "integration" and default_value == "auto"
156159
)
157-
if not is_auto_integration:
158-
try:
159-
WorkflowEngine._coerce_input(
160-
input_name, default_value, input_def
161-
)
162-
except ValueError as exc:
163-
errors.append(
164-
f"Input {input_name!r} has invalid default: {exc}"
165-
)
160+
validation_input_def: dict[str, Any] = input_def
161+
if is_auto_integration and "enum" in input_def:
162+
validation_input_def = {
163+
key: value
164+
for key, value in input_def.items()
165+
if key != "enum"
166+
}
167+
try:
168+
WorkflowEngine._coerce_input(
169+
input_name, default_value, validation_input_def
170+
)
171+
except ValueError as exc:
172+
errors.append(
173+
f"Input {input_name!r} has invalid default: {exc}"
174+
)
166175

167176
# -- Steps ------------------------------------------------------------
168177
if not isinstance(definition.steps, list):
@@ -737,7 +746,25 @@ def _resolve_inputs(
737746
)
738747
elif "default" in input_def:
739748
default_value = self._resolve_default(name, input_def["default"])
740-
resolved[name] = self._coerce_input(name, default_value, input_def)
749+
# If the integration default could not be resolved against
750+
# project state and falls back to the literal ``"auto"``
751+
# sentinel, exempt it from enum-membership coercion so a
752+
# workflow that lists specific integrations in ``enum`` does
753+
# not crash at runtime — declared type is still enforced.
754+
coerce_input_def = input_def
755+
if (
756+
name == "integration"
757+
and default_value == "auto"
758+
and "enum" in input_def
759+
):
760+
coerce_input_def = {
761+
key: value
762+
for key, value in input_def.items()
763+
if key != "enum"
764+
}
765+
resolved[name] = self._coerce_input(
766+
name, default_value, coerce_input_def
767+
)
741768
elif input_def.get("required", False):
742769
msg = f"Required input {name!r} not provided."
743770
raise ValueError(msg)

tests/test_workflows.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,29 @@ def test_validate_workflow_exempts_integration_auto_sentinel(self):
17071707
errors = validate_workflow(definition)
17081708
assert not any("invalid default" in e for e in errors), errors
17091709

1710+
def test_validate_workflow_still_checks_type_for_auto_sentinel(self):
1711+
"""The ``auto`` exemption only skips enum-membership; declared type is still enforced."""
1712+
from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow
1713+
1714+
definition = WorkflowDefinition.from_string("""
1715+
schema_version: "1.0"
1716+
workflow:
1717+
id: "auto-bad-type"
1718+
name: "Auto Bad Type"
1719+
version: "1.0.0"
1720+
inputs:
1721+
integration:
1722+
type: number
1723+
default: "auto"
1724+
steps:
1725+
- id: noop
1726+
type: gate
1727+
message: "noop"
1728+
options: [approve]
1729+
""")
1730+
errors = validate_workflow(definition)
1731+
assert any("invalid default" in e for e in errors), errors
1732+
17101733

17111734
# ===== State Persistence Tests =====
17121735

0 commit comments

Comments
 (0)