Skip to content

feat(api): add pack workflow execution via scope/name URL grammar#359

Merged
pocky merged 1 commit into
mainfrom
feature/F101-http-api--pack-workflow-execution-fix
May 29, 2026
Merged

feat(api): add pack workflow execution via scope/name URL grammar#359
pocky merged 1 commit into
mainfrom
feature/F101-http-api--pack-workflow-execution-fix

Conversation

@pocky
Copy link
Copy Markdown
Contributor

@pocky pocky commented May 29, 2026

Summary

  • Fixes pack workflow execution via the HTTP API: F097 listed pack workflows (e.g. speckit/specify) but every action endpoint (/run, /validate, GET) returned 404 because chi's single-segment {name} placeholder cannot match identifiers containing a /. The new (scope, name) two-segment URL grammar resolves this natively.
  • Replaces all single-segment workflow routes (/api/workflows/{name}[/run|/validate]) with {scope}/{name} equivalents. The scope sentinel local addresses non-pack workflows; a pack's vendor name serves as scope for pack workflows. This is a breaking change relative to the unreleased F097 API surface.
  • Fixes a nil-pointer panic in the TUI Workflows tab when validating a pack-sourced workflow: the wfMap lookup missed on fully-qualified packName/workflowName keys, causing handleValidate to dereference a nil wf pointer.
  • Closes a path-traversal vulnerability in the pack manifest loader: workflow names in manifest.yaml were previously not validated against the name regex, allowing a crafted ../../etc/passwd entry to escape the workflows/ directory via filepath.Join.

Changes

Domain / Ports

  • internal/domain/ports/repository.go: Add WorkflowSource type (env, local, global), WorkflowInfo struct, and ListWithSource method to WorkflowRepository interface
  • internal/domain/ports/repository_test.go: Implement ListWithSource on test mock
  • internal/domain/workflow/entry.go: Add Scope and Workflow fields to WorkflowEntry with full doc comment

Application

  • internal/application/service.go: Switch ListAllWorkflows from List to ListWithSource to populate Scope, Workflow, Source on each entry; extract promptFileError helper to eliminate repeated error construction
  • internal/application/service_test.go: Add tests for local/pack/mixed source propagation (AC2, AC3, AC5, AC6)

Infrastructure — Repository

  • internal/infrastructure/repository/yaml_repository.go: Add defaultSource field, WithSource constructor, and ListWithSource implementation
  • internal/infrastructure/repository/composite_repository.go: Migrate ListWithSource return type from internal WorkflowInfo to ports.WorkflowInfo; convert Source iota to ports.WorkflowSource
  • internal/infrastructure/repository/composite_repository_test.go: Update assertions to use ports.SourceLocal / ports.SourceGlobal

Infrastructure — Pack Discovery

  • internal/infrastructure/workflowpkg/manifest.go: Reject reserved scope names (local, global, env) with a structured USER.INPUT.VALIDATION_FAILED error; enforce name regex on every workflow entry in Workflows list
  • internal/infrastructure/workflowpkg/discoverer.go: Populate Scope and Workflow on each WorkflowEntry; add defense-in-depth regex guards for pack and workflow names in DiscoverWorkflows and loadWorkflowDescription
  • internal/infrastructure/workflowpkg/manifest_test.go: Add tests for invalid workflow names, valid kebab-case names, and reserved scope rejection with structured error assertions
  • internal/infrastructure/workflowpkg/discoverer_test.go: Add tests for path-traversal pack/workflow name skipping and Scope/Workflow field population

HTTP API Interface

  • internal/interfaces/api/routing.go: New file — recomposeIdentifier(scope, name) helper that maps local scope to bare name and any other scope to scope/name
  • internal/interfaces/api/routing_test.go: New file — table-driven tests for recomposeIdentifier
  • internal/interfaces/api/types.go: Add Scope path param to GetWorkflowInput, ValidateWorkflowInput, RunWorkflowInput; add Scope and Workflow fields to WorkflowSummary
  • internal/interfaces/api/handlers_workflows.go: Use recomposeIdentifier in Get and Validate; populate Scope/Workflow in List; update route paths to {scope}/{name}
  • internal/interfaces/api/handlers_executions.go: Use recomposeIdentifier in Run; update route path to {scope}/{name}/run
  • internal/interfaces/api/bridge.go: Expand TrackResumedExecution comment explaining intentional entry persistence
  • internal/interfaces/api/bridge_test.go: Extend mockWorkflowLister to record lastGetName/lastValidateName; add TestBridge_TrackResumedExecution_PersistsEntryForSubsequentQueries
  • internal/interfaces/api/doc.go: Update package doc to reflect {scope}/{name} route grammar
  • internal/interfaces/api/server_test.go: Update route path assertions to new grammar
  • internal/interfaces/api/handlers_workflows_test.go: Add newWorkflowHandlerAPI helper; add scope-routing tests; update all existing tests to new URL grammar
  • internal/interfaces/api/handlers_executions_test.go: Add newBlockingExecutionHandlerAPI helper; add pack/local scope execution tests; update all existing tests to new URL grammar

TUI

  • internal/interfaces/tui/tab_workflows.go: Fix handleValidate to forward item.entry.Name (fully-qualified) instead of item.wf.Name (nil for pack entries); extract resetInputForm helper to deduplicate identical five-field reset
  • internal/interfaces/tui/tab_workflows_test.go: Add TestWorkflowsTab_handleValidate_PackWorkflow_NoPanic and TestWorkflowsTab_resetInputForm_ClearsAllFields

Test Infrastructure & Fixtures

  • internal/testutil/mocks/mocks.go: Implement ListWithSource on MockWorkflowRepository
  • tests/fixtures/api/packs/speckit/manifest.yaml: New fixture pack manifest
  • tests/fixtures/api/packs/speckit/state.json: New fixture pack state
  • tests/fixtures/api/packs/speckit/workflows/specify.yaml: New fixture workflow for integration tests
  • tests/integration/api/pack_workflow_test.go: New integration test file — GET, run (with poll), validate, and SSE stream tests for pack workflows
  • tests/integration/api/functional_test.go: Update URLs to local/ scope prefix
  • tests/integration/api/server_integration_test.go: Update postRunWorkflow helper to local/ scope prefix

Docs & Config

  • docs/user-guide/api.md: Document (scope, name) grammar, update all examples to new URL shape, add pack workflow cURL/JS/Python examples
  • docs/user-guide/workflow-packs.md: Add "Reserved Pack Names" section documenting local/global restrictions
  • CHANGELOG.md: Document F101 breaking route change, pack loader reservation, TUI nil-pointer fix, path-traversal fix
  • .go-arch-lint.yml: Allow infra-workflowpkg to depend on domain-errors (needed for structured reservation error)

Test plan

  • make test passes with zero failures (unit tests cover recomposeIdentifier, scope field propagation, reserved name rejection, and TUI nil-pointer guard)
  • make test-integration passes: TestAPI_PackWorkflow_Get, TestAPI_PackWorkflow_Run, TestAPI_PackWorkflow_Validate, and TestAPI_PackWorkflow_SSE all reach 200/202/completed
  • make lint reports zero violations
  • Manual: awf serve, then curl http://localhost:2511/api/workflows/speckit/specify/run returns 202; curl http://localhost:2511/api/workflows/local/deploy-prod/run also returns 202

Closes #358


Generated with awf commit workflow

- `.go-arch-lint.yml`: allow infra-workflowpkg to depend on domain-errors
- `CHANGELOG.md`: document F101 route changes, fixes, and breaking change notes
- `docs/user-guide/api.md`: update all endpoint examples to (scope, name) two-segment URLs
- `docs/user-guide/workflow-packs.md`: add reserved pack names section for local/global/env
- `internal/application/service.go`: use ListWithSource for entry population; add Scope/Workflow fields; extract promptFileError helper
- `internal/application/service_test.go`: add tests for Scope/Workflow/Source propagation in ListAllWorkflows
- `internal/domain/ports/repository.go`: add WorkflowSource, WorkflowInfo types and ListWithSource to WorkflowRepository interface
- `internal/domain/ports/repository_test.go`: implement ListWithSource on mock
- `internal/domain/workflow/entry.go`: add Scope and Workflow fields with full doc comment
- `internal/domain/workflow/entry_test.go`: unit tests for WorkflowEntry field semantics
- `internal/infrastructure/repository/composite_repository.go`: migrate ListWithSource return type to ports.WorkflowInfo
- `internal/infrastructure/repository/composite_repository_test.go`: update assertions to ports.SourceLocal/SourceGlobal
- `internal/infrastructure/repository/yaml_repository.go`: add ListWithSource and WithSource for configurable source reporting
- `internal/infrastructure/workflowpkg/discoverer.go`: populate Scope/Workflow on pack entries; add defense-in-depth nameRegex guards
- `internal/infrastructure/workflowpkg/discoverer_test.go`: add traversal, invalid name, and scope/workflow field tests
- `internal/infrastructure/workflowpkg/manifest.go`: reject reserved scope names (local/global/env) and validate workflow names against nameRegex
- `internal/infrastructure/workflowpkg/manifest_test.go`: add reserved scope and invalid workflow name validation tests
- `internal/interfaces/api/bridge.go`: expand TrackResumedExecution comment explaining intentional persistence
- `internal/interfaces/api/bridge_test.go`: add Scope/Workflow fields to mock entries; add TrackResumedExecution persistence test
- `internal/interfaces/api/doc.go`: update route documentation to (scope, name) grammar
- `internal/interfaces/api/handlers_executions.go`: use recomposeIdentifier(scope, name) for workflow lookup; update route path
- `internal/interfaces/api/handlers_executions_test.go`: add pack/local scope run tests; extract newBlockingExecutionHandlerAPI helper
- `internal/interfaces/api/handlers_workflows.go`: pass Scope/Workflow to WorkflowSummary; use recomposeIdentifier; update route paths
- `internal/interfaces/api/handlers_workflows_test.go`: add scope/workflow field assertions; update paths to two-segment form; extract newWorkflowHandlerAPI helper
- `internal/interfaces/api/routing.go`: add recomposeIdentifier helper to build canonical workflow identifiers from scope+name
- `internal/interfaces/api/routing_test.go`: table-driven tests for recomposeIdentifier
- `internal/interfaces/api/server_test.go`: update test routes to (scope, name) form
- `internal/interfaces/api/types.go`: add Scope and Workflow fields to WorkflowSummary; add Scope to input structs
- `internal/interfaces/tui/tab_workflows.go`: fix nil-pointer panic in handleValidate by using fully-qualified entry name
- `internal/interfaces/tui/tab_workflows_test.go`: add tests for pack workflow validate path
- `internal/testutil/mocks/mocks.go`: implement ListWithSource on mock repository
- `tests/fixtures/api/packs/speckit/manifest.yaml`: add speckit pack fixture manifest
- `tests/fixtures/api/packs/speckit/state.json`: add speckit pack state fixture
- `tests/fixtures/api/packs/speckit/workflows/specify.yaml`: add speckit/specify workflow fixture
- `tests/integration/api/functional_test.go`: update route paths to (scope, name) form
- `tests/integration/api/pack_workflow_test.go`: integration tests for pack workflow list/get/run/validate via HTTP API
- `tests/integration/api/server_integration_test.go`: update route path in integration test

Closes #358
@pocky pocky marked this pull request as ready for review May 29, 2026 09:19
@pocky pocky merged commit 7df540b into main May 29, 2026
5 checks passed
@pocky pocky deleted the feature/F101-http-api--pack-workflow-execution-fix branch May 29, 2026 09:19
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.

F101: HTTP API — Pack Workflow Execution Fix

1 participant