Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .go-arch-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ deps:
infra-workflowpkg:
mayDependOn:
- domain-workflow
- domain-errors
- infra-repository
- pkg-registry
- pkg-httpx
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **F101**: HTTP API workflow routes restructured around a `(scope, name)` two-segment URL grammar to support pack-workflow execution. New routes: `GET /api/workflows/{scope}/{name}`, `POST /api/workflows/{scope}/{name}/run`, `POST /api/workflows/{scope}/{name}/validate`. The `scope` sentinel `local` addresses non-pack workflows (e.g. `/api/workflows/local/deploy-prod/run`); the vendor (pack) name serves as scope for pack workflows (e.g. `/api/workflows/speckit/specify/run`). `WorkflowSummary` gains additive `scope` and `workflow` fields alongside the canonical `name` so clients can build operation URLs directly without splitting on `/`. **Breaking** (F097 not yet released): the previous single-segment routes `/api/workflows/{name}[/run|/validate]` are removed; clients targeting F097 routes must migrate. OpenAPI 3.1 document at `/openapi.json` reflects the new path parameters automatically. SSE event streaming at `/api/executions/{id}/events` is unaffected (keyed by execution UUID).
- **F101**: Pack loader (`internal/infrastructure/workflowpkg/`) now rejects manifests whose `name` matches the reserved scope tokens `local` or `global` with a structured `USER.INPUT.VALIDATION_FAILED` error containing `pack_name` and `reserved_tokens` in the details map. Enforcement is single-source at the manifest validator and applies on install, on discovery listing, and on any future pack-aware code path. Names that merely contain or prefix-share with reserved tokens (`localpack`, `globalpack`, `run`, `validate`) remain valid.

### Fixed

- **F101**: Pack workflows are now executable over the HTTP API. F097 advertised pack workflows in `GET /api/workflows` but the single-segment `{name}` placeholder in chi could not match canonical identifiers containing `/` (e.g. `speckit/specify`), so every action endpoint returned `404 Not Found`. The new `(scope, name)` grammar resolves pack identifiers natively and restores parity between `GET /api/workflows` listing and the read/run/validate operations.
- **F101**: TUI `Workflows` tab no longer panics when a user triggers validation on a pack-sourced workflow. Pack entries carry `Name == "packName/workflowName"` while the resolved workflow value object only knows the bare name, so the internal `wfMap` lookup missed and `handleValidate` dereferenced a nil pointer. The validate handler now forwards the fully-qualified entry name (already accepted by the validation service) — same approach used by the existing nil guard in `handleLaunch`.
- **F101**: Workflow pack manifests with traversal-style names are now rejected. `Manifest.Validate` previously applied `^[a-z][a-z0-9-]*$` only to the pack name; entries in the `workflows:` list reached `filepath.Join` unchecked, so a manifest declaring `workflows: ["../../etc/passwd"]` could escape `workflowsDir` to whatever file happened to exist at the resolved path. The same regex is now enforced on every workflow name during validation. The pack discoverer adds defense-in-depth, silently skipping packs and workflow entries whose names fail the regex even when validation is bypassed.

### Breaking Changes

- **F100**: Dedicated `roles/` namespace for agent roles — the environment variable for overriding role search paths is renamed from `AWF_AGENTS_PATH` to `AWF_ROLES_PATH`. All role discovery now happens exclusively under `roles/`-namespaced directories. Migration guide for users upgrading from F098:
Expand Down
214 changes: 194 additions & 20 deletions docs/user-guide/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ Once running:

## Endpoints

Workflows are identified by a `(scope, name)` tuple in the URL path:
- **Local workflows**: `scope = "local"`, e.g., `GET /api/workflows/local/deploy-prod`
- **Pack workflows**: `scope = "<pack-name>"`, e.g., `GET /api/workflows/speckit/specify`

This two-segment grammar replaces the prior single-segment `{name}` placeholder, enabling support for pack workflows which previously resolved with URL mismatches. The tokens `local` and `global` are **reserved scope sentinels** — pack manifests cannot use them as `name`. See [Workflow Packs — Reserved Pack Names](workflow-packs.md#reserved-pack-names).

### Workflow Discovery & Validation

#### List workflows
Expand All @@ -59,32 +65,79 @@ GET /api/workflows
"workflows": [
{
"name": "code-review",
"scope": "local",
"workflow": "code-review",
"version": "1.0.0",
"description": "Review code for bugs and security issues"
},
{
"name": "deploy-app",
"version": "2.1.0",
"description": "Deploy application to production"
"name": "speckit/specify",
"scope": "speckit",
"workflow": "specify",
"version": "1.0.0",
"description": "Specification-driven workflow"
}
]
}
}
```

#### Get workflow details
**Fields:**
- `name` — Canonical workflow identifier (`scope/workflow` for packs, plain name for local)
- `scope` — Scope token (`local` for non-pack, pack name for pack workflows)
- `workflow` — Local part of the workflow name (without scope prefix)
- `version` — Semantic version
- `description` — Brief description

Clients build operation URLs from the `scope` and `workflow` fields: `GET /api/workflows/{scope}/{workflow}`.

#### Get workflow details (local)

```http
GET /api/workflows/{name}
GET /api/workflows/local/{name}
```

**Example:**
```bash
curl http://localhost:2511/api/workflows/local/deploy-prod
```

**Response (200 OK):**
```json
{
"body": {
"name": "code-review",
"name": "deploy-prod",
"version": "1.0.0",
"description": "Review code for bugs and security issues",
"description": "Deploy application to production",
"states": {
"initial": "build",
"build": {
"type": "step",
"command": "go build ./cmd/app"
}
}
}
}
```

#### Get workflow details (pack)

```http
GET /api/workflows/{pack}/{name}
```

**Example:**
```bash
curl http://localhost:2511/api/workflows/speckit/specify
```

**Response (200 OK):**
```json
{
"body": {
"name": "specify",
"version": "1.0.0",
"description": "Specification-driven workflow",
"states": {
"initial": "read",
"read": {
Expand All @@ -101,14 +154,21 @@ GET /api/workflows/{name}
{
"status": 404,
"title": "Not Found",
"detail": "workflow not found: nonexistent"
"detail": "workflow not found"
}
```

#### Validate workflow
Returned uniformly for: unknown scopes, unknown workflows within a known scope, and unknown packs.

#### Validate workflow (local)

```http
POST /api/workflows/{name}/validate
POST /api/workflows/local/{name}/validate
```

**Example:**
```bash
curl -X POST http://localhost:2511/api/workflows/local/deploy-prod/validate
```

**Response (200 OK — valid workflow):**
Expand All @@ -120,6 +180,17 @@ POST /api/workflows/{name}/validate
}
```

#### Validate workflow (pack)

```http
POST /api/workflows/{pack}/{name}/validate
```

**Example:**
```bash
curl -X POST http://localhost:2511/api/workflows/speckit/specify/validate
```

**Response (200 OK — invalid workflow):**
```json
{
Expand All @@ -133,10 +204,10 @@ POST /api/workflows/{name}/validate

### Workflow Execution

#### Run workflow (async)
#### Run workflow (local)

```http
POST /api/workflows/{name}/run
POST /api/workflows/local/{name}/run
Content-Type: application/json

{
Expand All @@ -147,6 +218,13 @@ Content-Type: application/json
}
```

**Example:**
```bash
curl -X POST http://localhost:2511/api/workflows/local/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "main.go"}}'
```

**Response (202 Accepted):**
```json
{
Expand All @@ -159,15 +237,50 @@ Content-Type: application/json

The workflow begins execution asynchronously. Use the `execution_id` to monitor progress via the events endpoint or polling.

#### Run workflow (pack)

```http
POST /api/workflows/{pack}/{name}/run
Content-Type: application/json

{
"inputs": {
"feature": "F101"
}
}
```

**Example:**
```bash
curl -X POST http://localhost:2511/api/workflows/speckit/specify/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"feature": "F101"}}'
```

**Response (202 Accepted):**
```json
{
"body": {
"execution_id": "661f9600-e39c-52e5-c827-557766552000",
"status": "accepted"
}
}
```

**Error (404 Not Found):**
```json
{
"status": 404,
"title": "Not Found",
"detail": "workflow not found: nonexistent"
"detail": "workflow not found"
}
```

Returned when:
- The scope (pack name or `local`) does not exist
- The workflow name does not exist in that scope
- The pack is not installed

**Error (422 Unprocessable Entity):**
```json
{
Expand Down Expand Up @@ -403,9 +516,10 @@ open http://localhost:2511/docs

### cURL

**Run a local workflow:**
```bash
# Run a workflow
RESULT=$(curl -s -X POST http://localhost:2511/api/workflows/code-review/run \
# Run a local workflow
RESULT=$(curl -s -X POST http://localhost:2511/api/workflows/local/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "main.go"}}')

Expand All @@ -418,11 +532,25 @@ curl -N http://localhost:2511/api/executions/$EXEC_ID/events
curl http://localhost:2511/api/executions/$EXEC_ID
```

**Run a pack workflow:**
```bash
# Run a pack workflow
RESULT=$(curl -s -X POST http://localhost:2511/api/workflows/speckit/specify/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"feature": "F101"}}')

EXEC_ID=$(echo $RESULT | jq -r '.body.execution_id')

# Stream events
curl -N http://localhost:2511/api/executions/$EXEC_ID/events
```

### JavaScript/TypeScript

**Run a local workflow:**
```typescript
// Start execution
const response = await fetch('http://localhost:2511/api/workflows/code-review/run', {
const response = await fetch('http://localhost:2511/api/workflows/local/code-review/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inputs: { file: 'main.go' } })
Expand All @@ -446,15 +574,29 @@ eventSource.addEventListener('workflow.completed', (event) => {
});
```

**Run a pack workflow:**
```typescript
// Start pack workflow execution
const response = await fetch('http://localhost:2511/api/workflows/speckit/specify/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inputs: { feature: 'F101' } })
});

const { body } = await response.json();
const executionId = body.execution_id;
```

### Python

**Run a local workflow:**
```python
import requests
import json

# Start execution
response = requests.post(
'http://localhost:2511/api/workflows/code-review/run',
'http://localhost:2511/api/workflows/local/code-review/run',
json={'inputs': {'file': 'main.go'}}
)

Expand All @@ -472,6 +614,19 @@ for line in response.iter_lines():
print(f'Event: {event_type}')
```

**Run a pack workflow:**
```python
import requests

# Start pack workflow execution
response = requests.post(
'http://localhost:2511/api/workflows/speckit/specify/run',
json={'inputs': {'feature': 'F101'}}
)

execution_id = response.json()['body']['execution_id']
```

## Error Handling

All error responses follow RFC 7807 Problem Details format (provided by Huma):
Expand Down Expand Up @@ -526,6 +681,7 @@ kill -TERM $(pgrep -f "awf serve") # or Ctrl+C in foreground

### Full workflow execution flow

**Local workflow example:**
```bash
# 1. Start server
awf serve --port 8080 &
Expand All @@ -534,10 +690,10 @@ awf serve --port 8080 &
curl http://localhost:8080/api/workflows

# 3. Validate a workflow before running
curl -X POST http://localhost:8080/api/workflows/code-review/validate
curl -X POST http://localhost:8080/api/workflows/local/code-review/validate

# 4. Start a workflow execution
RESPONSE=$(curl -s -X POST http://localhost:8080/api/workflows/code-review/run \
RESPONSE=$(curl -s -X POST http://localhost:8080/api/workflows/local/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "src/main.go"}}')

Expand All @@ -557,6 +713,23 @@ curl "http://localhost:8080/api/history?workflow=code-review&limit=10"
curl http://localhost:8080/api/history/stats?workflow=code-review
```

**Pack workflow example:**
```bash
# 1. Start server with pack installed
awf workflow install myorg/awf-workflow-speckit
awf serve --port 8080 &

# 2. Run a pack workflow
RESPONSE=$(curl -s -X POST http://localhost:8080/api/workflows/speckit/specify/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"feature": "F101"}}')

EXEC_ID=$(echo $RESPONSE | jq -r '.body.execution_id')

# 3. Monitor the execution
curl -N http://localhost:8080/api/executions/$EXEC_ID/events
```

### Integrate with CI/CD (GitHub Actions)

```yaml
Expand All @@ -571,7 +744,8 @@ jobs:

- name: Run AWF code review via API
run: |
RESPONSE=$(curl -s -X POST http://awf-server:2511/api/workflows/code-review/run \
# Run a local workflow via the API
RESPONSE=$(curl -s -X POST http://awf-server:2511/api/workflows/local/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "src/main.go"}}')

Expand Down
Loading
Loading