Skip to content

feat(workflow-executor): add HTTP server and WorkflowRunner scaffold#1500

Merged
matthv merged 14 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feature/prd-222-implement-executor-http-server
Mar 20, 2026
Merged

feat(workflow-executor): add HTTP server and WorkflowRunner scaffold#1500
matthv merged 14 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feature/prd-222-implement-executor-http-server

Conversation

@matthv
Copy link
Member

@matthv matthv commented Mar 19, 2026

Summary

  • Adds ExecutorHttpServer (Koa) with two routes:
    • GET /runs/:runId — reads records + steps from RunStoreFactory
    • POST /runs/:runId/trigger — delegates to WorkflowRunner.triggerPoll()
  • Adds WorkflowRunner class (stub) as the main entry point, aligned with spec section 4.1. Starts HTTP server on start() if httpPort is configured
  • Adds RunStoreFactory interface to resolve runId → RunStore

Test plan

  • 6 unit tests via supertest (GET 200/404, POST trigger, error propagation, start/stop)
  • Lint + build pass

fixes PRD-222

🤖 Generated with Claude Code

Note

Add Koa HTTP server and WorkflowRunner scaffold to workflow-executor

  • Adds ExecutorHttpServer, a Koa-based HTTP server with GET /runs/:runId (returns step executions or 404) and POST /runs/:runId/trigger (invokes WorkflowRunner.triggerPoll).
  • Adds WorkflowRunner, which manages the HTTP server lifecycle via start()/stop(); triggerPoll is currently a no-op stub.
  • Adds the RunStoreFactory interface for looking up a RunStore by run ID.
  • Exports all new types and classes from the package index.
  • Risk: handleTrigger does not catch errors from triggerPoll, so failures propagate as unhandled 500s via Koa's default error handler.

Changes since #1500 opened

  • Renamed RunStoreFactory.getRunStore method to buildRunStore and updated all call sites in ExecutorHttpServer.handleGetRun handler and test files to use the new method name [a78e559]
  • Renamed workflow-runner.ts to runner.ts and updated import paths from '../workflow-runner' and '../../src/workflow-runner' to '../runner' and '../../src/runner' respectively across ExecutorHttpServer, the package index exports, and test files [a78e559]
  • Modified ExecutorHttpServer.stop method to defer setting this.server to null until inside the HTTP server close callback, ensuring the server reference remains non-null during the asynchronous shutdown process [a78e559]
  • Renamed WorkflowRunner class to Runner and WorkflowRunnerConfig interface to RunnerConfig throughout the workflow-executor package [0d6abe8]
  • Updated documentation to reflect new scaffold architecture [0670c16]
  • Added error handling to ExecutorHttpServer.start method to reject the promise when the underlying HTTP server emits an error event during startup, and added guard logic to Runner.start method to prevent creating multiple ExecutorHttpServer instances when httpPort is configured [a7f17f2]
  • Added validation for missing runId parameter in HTTP handlers [0157cb1]
  • Removed nullability from RunStoreFactory.buildRunStore return type and corresponding null-check logic in ExecutorHttpServer.handleGetRun [afd0743]
  • Updated test suite to accommodate non-null RunStoreFactory contract [afd0743]
  • Relocated run-store-factory.ts module from src/http/ to src/ directory and updated all import paths [40d44d1]
  • Added test suite for Runner class HTTP server lifecycle management [42c459d]
  • Added test for Runner.triggerPoll method [42c459d]
  • Updated the import path for RunStore type in the run-store-factory module [d2d4ff5]
  • Added global error-handling middleware to ExecutorHttpServer and removed explicit parameter validation from route handlers [2ed53b6]
  • Modified Runner.start to support HTTP server initialization on port 0 and fixed ExecutorHttpServer.stop to preserve server instance on close failure [2ed53b6]
  • Made executionResult optional in ConditionStepExecutionData interface [2ed53b6]
  • Updated tests to validate structured error responses and constructor arguments [2ed53b6]
  • Made executionResult property required in ConditionStepExecutionData interface [fe50017]
  • Flattened RunnerConfig interface structure by replacing nested ports object with top-level agentPort and workflowPort properties [19926eb]
  • Changed RunStore interface methods to accept runId as a parameter [b6383ef]
  • Removed RunStoreFactory interface and its exports [b6383ef]
  • Updated ExecutorHttpServer to accept RunStore instead of RunStoreFactory [b6383ef]
  • Updated RunnerConfig to accept RunStore instead of RunStoreFactory [b6383ef]
  • Updated all step executor implementations to pass runId to RunStore methods [b6383ef]
  • Updated all test files to match new RunStore API signatures [b6383ef]
📊 Macroscope summarized 12bc2be. 5 files reviewed, 2 issues evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@linear
Copy link

linear bot commented Mar 19, 2026


private async handleGetRun(ctx: Koa.Context): Promise<void> {
const { runId } = ctx.params;
const runStore = this.options.runStoreFactory.getRunStore(runId);
Copy link
Member

Choose a reason for hiding this comment

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

buildRunStore

Adds ExecutorHttpServer (Koa) with two routes:
- GET /runs/:runId — reads records + steps from RunStore
- POST /runs/:runId/trigger — delegates to WorkflowRunner.triggerPoll()

WorkflowRunner is a concrete class (stub) aligned with spec section 4.1.
Polling and execution dispatch will be implemented in a follow-up PR.

fixes PRD-222

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@matthv matthv force-pushed the feature/prd-222-implement-executor-http-server branch from 88ef7d5 to 12bc2be Compare March 19, 2026 15:03
import type { RunStore } from '../ports/run-store';

export interface RunStoreFactory {
getRunStore(runId: string): RunStore | null;
Copy link
Member

Choose a reason for hiding this comment

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

rename get by build

- Rename getRunStore → buildRunStore (Scra3)
- Rename workflow-runner.ts → runner.ts (Scra3)
- Fix stop() race condition: move this.server = null inside close callback (Macroscope)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@qltysh
Copy link

qltysh bot commented Mar 19, 2026

Qlty

Coverage Impact

Unable to calculate total coverage change because base branch coverage was not found.

Modified Files with Diff Coverage (6)

RatingFile% DiffUncovered Line #s
New file Coverage rating: A
packages/workflow-executor/src/runner.ts100.0%
New file Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
New file Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/http/executor-http-server.ts97.4%60
New file Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Total98.2%
🤖 Increase coverage with AI coding...

In the `feature/prd-222-implement-executor-http-server` branch, add test coverage for this new code:

- `packages/workflow-executor/src/http/executor-http-server.ts` -- Line 60

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@matthv
Copy link
Member Author

matthv commented Mar 19, 2026

Addressed all review feedback:

  • getRunStorebuildRunStore
  • workflow-runner.tsrunner.ts
  • stop() race condition fixed — this.server = null moved inside close callback ✅

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
import type { RunStore } from '../ports/run-store';

export interface RunStoreFactory {
buildRunStore(runId: string): RunStore | null;
Copy link
Member

Choose a reason for hiding this comment

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

👀 Can really return null?

matthv and others added 4 commits March 19, 2026 16:23
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Guard against double start() in Runner (Macroscope)
- Handle listen() error in start() via server.on('error', reject) (Macroscope)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Return 400 when runId is missing on both GET /runs/:runId
and POST /runs/:runId/trigger.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Factory always builds a RunStore for the given runId.
runId validation (400) is handled before calling buildRunStore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@matthv
Copy link
Member Author

matthv commented Mar 19, 2026

Final summary of all fixes applied:

Renames:

  • getRunStorebuildRunStore
  • workflow-runner.tsrunner.ts
  • WorkflowRunnerRunner, WorkflowRunnerConfigRunnerConfig

Bug fixes:

  • stop() race condition — this.server = null moved inside close callback
  • start() hang on port conflict — added server.on('error', reject)
  • Double start() guard — if (httpPort && !this.httpServer)

Validation:

  • Both routes return 400 if runId is missing

Interface change:

  • buildRunStore no longer returns null — it always builds a RunStore for the given runId. Validation happens before the call.

Docs:

  • CLAUDE.md updated with new modules (runner, adapters, http)

matthv and others added 7 commits March 19, 2026 16:55
RunStoreFactory is not HTTP-specific — it's also used by the Runner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests start/stop lifecycle, HTTP server wiring, double-start guard,
restart after stop, and triggerPoll stub.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… types

- Add Koa error middleware to ExecutorHttpServer (returns structured JSON on errors)
- Use server.once('error') instead of .on() to avoid stale listener after start
- Fix stop(): only null this.server on successful close, not on error
- Fix Runner.start(): use !== undefined check for httpPort (avoids falsy 0 bug)
- Fix Runner.start(): assign this.httpServer only after server.start() resolves
- Make ConditionStepExecutionData.executionResult optional (matches runtime behavior)
- Add GET /runs/:runId error path test (500 + structured body)
- Assert buildRunStore called with correct runId in GET test
- Assert response.body.error in trigger error test
- Strengthen Runner constructor assertion to verify full config object
- Remove module-level shared mock state in executor-http-server tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onResult as required

Out of scope for this PR — will be addressed separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tly to RunStore methods

RunStoreFactory was a superfluous abstraction (YAGNI) — its only role was to
scope a RunStore to a runId, but all callers already know their runId. Adding
runId directly to RunStore.getStepExecutions/saveStepExecution removes one
level of indirection, one file, and simplifies RunnerConfig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@matthv matthv merged commit 613ec1b into feat/prd-214-setup-workflow-executor-package Mar 20, 2026
30 checks passed
@matthv matthv deleted the feature/prd-222-implement-executor-http-server branch March 20, 2026 08:27
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