Skip to content

feat(workflows): install custom step types from local dirs and archives - #4757

Open
markuswondrak wants to merge 2 commits into
github:mainfrom
markuswondrak:fix/4695-local-step-install
Open

markuswondrak wants to merge 2 commits into
github:mainfrom
markuswondrak:fix/4695-local-step-install

Conversation

@markuswondrak

Copy link
Copy Markdown
Contributor

Summary

Implements #4695 only. This replacement for #4754 is based directly on current main; it intentionally excludes the unrelated workflow-composition implementation from #4680.

  • Adds workflow step add --dev <directory> and --from <archive-url> with shared package validation, provenance, trust confirmation, and --force handling.
  • Makes step registry updates atomic, validates persisted metadata, serializes install/remove mutations, and hardens staging/cleanup behavior.
  • Refreshes project-local custom step modules, documents package transport and loading behavior, and adds regression coverage.

Evidence

Regression coverage exercises YAML-native metadata rejection, atomic registry serialization failure, staged metadata changes, archive declaration mismatch after redirects, Rich markup package names, runtime refresh between projects, and bundle step delegation.

Validation

  • .venv/bin/python -m pytest tests/specify_cli/workflows/step tests/specify_cli/workflows/test_custom_steps.py tests/specify_cli/bundles/test_primitives.py tests/specify_cli/bundles/test_references.py -q
    • 185 passed
  • Focused Ruff gate passes.

AI Disclosure

Implementation was generated with OpenCode (models: deepseek-v4.1-flash for the original feature and gpt-5.6-terra for remediation), autonomous mode. The AI authored code, tests, documentation, commits, and this PR description on behalf of @markuswondrak.

Markus added 2 commits September 25, 2026 19:26
…es (github#4695)

`specify workflow step add` gains `--dev <directory>` and `--from <archive-url>` alongside the existing catalog source. All three converge on a new `step/installer.py` domain module that owns package validation (shape, symlink/special-file rejection, 512-file/50 MiB limits), same-filesystem staging with revalidation, atomic commit, `--force` replacement, and source-kind-only registry provenance. Direct URLs require a default-deny trust prompt before any request.

Docs document the local-authoring flow and the deferred bundle-local limitation.

Assisted-by: opencode (model: deepseek-v4.1-flash, autonomous)
Assisted-by: OpenCode (model: gpt-5.6-terra, autonomous)
Copilot AI balanced review requested due to automatic review settings September 25, 2026 17:27

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.

Copilot review overview

🟡 Changes recommended

Removal staging can reload uninstalled code, directory-copy safety has a symlink race, and unrelated composition documentation is included.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
What changed in this PR

Adds local-directory and archive-URL installation for custom workflow steps, with shared validation, atomic registry persistence, runtime refresh, and removal handling.

Changes:

  • Adds --dev, --from, and --force installation flows.
  • Centralizes package validation, staging, provenance, locking, and cleanup.
  • Expands documentation and regression coverage.
File Description
src/​specify_cli/​workflows/​step/​installer.py Implements shared installation and removal logic.
src/​specify_cli/​workflows/​step/​command_add.py Adds local and archive CLI sources.
src/​specify_cli/​workflows/​step/​command_remove.py Delegates transactional removal.
src/​specify_cli/​workflows/​step/​command_info.py Displays installation provenance.
src/​specify_cli/​workflows/​step/​_helpers.py Wraps shared installer validation.
src/​specify_cli/​workflows/​step/​catalog/​_domain.py Makes registry writes atomic.
src/​specify_cli/​workflows/​__init__.py Refreshes project-local step modules.
docs/​reference/​workflows.md Documents custom packages and composition.
docs/​reference/​bundles.md Documents bundle-local step limitations.
tests/​specify_cli/​workflows/​test_custom_steps.py Tests runtime step refresh.
tests/​specify_cli/​workflows/​step/​test_installer.py Covers installer validation and failures.
tests/​specify_cli/​workflows/​step/​test_command_add.py Covers new installation sources.
tests/​specify_cli/​workflows/​step/​test_command_remove.py Covers removal behavior.
tests/​specify_cli/​workflows/​step/​test_command_info.py Tests provenance output.
tests/​specify_cli/​workflows/​step/​test_command_list.py Updates list-command tests.
tests/​specify_cli/​workflows/​step/​test_command_search.py Updates search-command tests.
tests/​specify_cli/​workflows/​step/​catalog/​test_registry.py Tests atomic registry failure handling.
tests/​specify_cli/​workflows/​step/​catalog/​test_command_list.py Updates catalog-list tests.
tests/​specify_cli/​bundles/​test_primitives.py Tests bundle step delegation.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +545 to +546
if stat.S_ISDIR(mode):
_copy(Path(entry.path), target)
Comment on lines +798 to +802
staged_dir = Path(
tempfile.mkdtemp(
prefix=f".{step_id}.removing-", dir=steps_base_dir
)
)
Comment on lines +691 to +695
### Workflow composition (`type: workflow`)

A `workflow` step runs an installed workflow as a **scoped subtree of the
current run** — there is one run, one run directory, and one process. The
included workflow behaves like a function call: values cross the boundary only

This branch has not been deployed

No deployments
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.

2 participants