Skip to content

fix(workflows): harden catalog.py against mis-shaped registry & non-string fields#3375

Open
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/workflow-catalog-malformed-input
Open

fix(workflows): harden catalog.py against mis-shaped registry & non-string fields#3375
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/workflow-catalog-malformed-input

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Description

Two robustness gaps in workflows/catalog.py where the Workflow* classes diverge from their Step* siblings, which already guard both cases:

1. WorkflowRegistry._load returns json.load() verbatim. A JSON-valid but mis-shaped registry file crashes every method that indexes data["workflows"]:

  • workflow-registry.json = []is_installed/get/list/remove/add raise TypeError: list indices must be integers;
  • = {"schema_version": "1.0"} (no workflows key) → KeyError: 'workflows'.

StepRegistry._load already validates the shape (dict root + dict steps) and resets to default. Reproduced on main @ 92b7cf7.

2. WorkflowCatalog.search joins name/description/id without coercion. A catalog entry with a null or non-string field raises TypeError: sequence item 0: expected str instance, NoneType found. StepCatalog.search already coerces with str(... or "") — and has a regression test for exactly this; WorkflowCatalog.search was the outlier.

Fix

Mirror the siblings: validate the registry shape in _load (+ widen the except tuple to OSError/UnicodeError), and coerce the search fields with str(wf_data.get(...) or "").

Testing

  • test_load_tolerates_misshaped_registry (parametrized [] / no-workflows): registry recovers to the default shape and all methods run. Fails before (TypeError/KeyError).
  • test_search_with_non_string_fields (WorkflowCatalog, mirrors the existing StepCatalog test): null/int fields no longer raise. Fails before (TypeError).
  • Both verified fail-before via source-stash; TestWorkflowRegistry + the new catalog test pass; uvx ruff check clean.

AI Disclosure

  • I did use AI assistance (describe below)

Found and fixed with Claude Code (Claude Fable 5) under my direction. AI diffed the Workflow* classes against their Step* siblings; I reproduced both crashes, verified fail-before/pass-after, and reviewed the diff.

…tring fields

Two robustness gaps where WorkflowRegistry/WorkflowCatalog diverged from
their StepRegistry/StepCatalog siblings, which already guard both:

- WorkflowRegistry._load returned json.load() verbatim, so a JSON-valid
  but mis-shaped registry (a list root, or a dict lacking a 'workflows'
  mapping) made is_installed/get/list/remove/add crash with
  TypeError/KeyError. Mirror StepRegistry._load: validate the shape and
  reset to default, and widen the except tuple to OSError/UnicodeError.
- WorkflowCatalog.search joined name/description/id without coercion, so a
  null or non-string field raised TypeError. Coerce with str(... or '')
  exactly as StepCatalog.search does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens specify_cli.workflows.catalog to better tolerate malformed on-disk registries and non-string catalog data, aligning Workflow* behavior with the existing Step* implementations.

Changes:

  • Validate workflow-registry.json shape in WorkflowRegistry._load() and fall back to a default structure for mis-shaped/corrupt content.
  • Coerce WorkflowCatalog.search() searchable fields to strings to avoid TypeError on null/non-string values.
  • Add regression tests covering mis-shaped registry files and non-string workflow fields during search.
Show a summary per file
File Description
tests/test_workflows.py Adds regression tests for mis-shaped workflow registry loading and non-string workflow catalog search fields.
src/specify_cli/workflows/catalog.py Adds registry shape validation in _load() and coerces search fields to strings for robustness.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread tests/test_workflows.py Outdated
Per review: WorkflowRegistry.list() always returns a dict, so assert
'== {}' directly (the previous '== {} or == []' called list() twice and
admitted a shape it never returns), and reference
WorkflowRegistry.SCHEMA_VERSION instead of hard-coding '1.0'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants