fix(migration): treat an unselected runner id as not_legacy

A saved pipeline may carry the post-migration shape
{'ai': {'runner': {'id': ''}, 'runner_config': {}}} while no runner has been
selected yet. The planner matched the empty id against the certified
'plugin:author/name/component' form, failed, and returned a malformed-id
blocker, so the whole 'migrate all' batch stopped with both pipelines reported
as blocked even though there was no legacy section and nothing to convert.

A blank id with no legacy runner section now reports not_legacy: no work is
needed and no target is synthesized. A blank id that still coexists with a
legacy section remains an explicit invalid_runner_id blocker, because that
ambiguous state must be resolved by the operator rather than guessed.
This commit is contained in:
TyperBody
2026-09-25 20:02:17 +08:00
parent afe700f1d8
commit c76fd2d7c5
2 changed files with 35 additions and 1 deletions
@@ -634,6 +634,12 @@ def plan_legacy_pipeline(config, extensions_preferences=None) -> dict:
return _block(result, 'mixed_runner_selection', 'ai.runner')
if 'id' in selection:
current = selection['id']
# A blank runner id paired with no legacy runner section means the saved
# pipeline simply has no runner selected. There is nothing to migrate and
# nothing to convert, so report not_legacy instead of failing the whole
# batch with a malformed-id blocker.
if type(current) is str and not current.strip() and not any(legacy in ai for legacy in _TARGETS):
return result
if type(current) is not str or not re.fullmatch(r'plugin:[^/\s]+/[^/\s]+/[^/\s]+', current):
return _block(result, 'invalid_runner_id', 'ai.runner.id')
result['state'] = 'already_current'