Skip to content

refactor(workflow-executor): extract ActionRef/FieldRef, align Ref shapes#1502

Open
Scra3 wants to merge 31 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/trigger-action-step-executor
Open

refactor(workflow-executor): extract ActionRef/FieldRef, align Ref shapes#1502
Scra3 wants to merge 31 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/trigger-action-step-executor

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Mar 20, 2026

Summary

  • Extract ActionRef and FieldRef shared interfaces ({ name, displayName }) from inline types in TriggerActionStepExecutionData and UpdateRecordStepExecutionData
  • Replace FieldReadBase (private) with exported FieldRefFieldReadSuccess/FieldReadError now extend FieldRef
  • Rename actionDisplayName/actionNamedisplayName/name and fieldDisplayName/fieldNamedisplayName/name for consistency
  • Move resolveFieldName to handleFirstCall so the technical field name is stored in pendingUpdate at creation time (no re-resolution in resolveAndUpdate)
  • Add missing tests: resolveActionName not-found path, saveStepExecution not-called assertions on WorkflowExecutorError, trigger-action pending action in buildStepSummary
  • Export ActionRef, FieldRef, NoActionsError from index; update CLAUDE.md

Test plan

  • yarn workspace @forestadmin/workflow-executor test — 144 tests pass
  • yarn workspace @forestadmin/workflow-executor build — no errors
  • yarn workspace @forestadmin/workflow-executor lint — clean

🤖 Generated with Claude Code

Note

Extract ActionRef/FieldRef types and add UpdateRecordStepExecutor/TriggerRecordActionStepExecutor

  • Introduces RecordTaskStepDefinition and RecordTaskStepExecutionData (replacing AiTask* equivalents), and adds FieldRef/ActionRef as shared named-field reference shapes across execution data types.
  • Adds two new executors: UpdateRecordStepExecutor (selects a field and value, supports confirmation flow) and TriggerRecordActionStepExecutor (selects and triggers a collection action, supports confirmation flow).
  • Extracts shared confirmation-flow logic into a new RecordTaskStepExecutor base class; BaseStepExecutor gains schema caching, selectRecordRef, findField, and getAvailableRecordRefs helpers.
  • BaseStepExecutor.execute now catches WorkflowExecutorError and returns a structured error outcome instead of throwing; context.history is renamed to context.previousSteps.
  • Risk: AiTaskStepOutcome, AiTaskStepStatus, AiTaskStepExecutionData, and UserInput are removed from the public API; callers must migrate to RecordTask* equivalents.

Macroscope summarized c8b9247.

alban bertolini and others added 25 commits March 20, 2026 09:30
…on flow

Implements PRD-219. Adds an executor for update-record steps with three
execution branches: automatic completion, awaiting user confirmation, and
re-entry after confirmation. Extracts shared record selection helpers from
ReadRecordStepExecutor into BaseStepExecutor for reuse.

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

Match on type + stepIndex only, then guard on toolConfirmationInterruption
presence separately. Removes redundant filter predicate.

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

The field stores the proposed field update (from AI or user-edited) pending
confirmation. "pendingUpdate" better describes the content than
"toolConfirmationInterruption" which described the mechanism.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…f error outcome

Missing pendingUpdate in Branch A is a bug (orchestrator/RunStore), not a
business case — throw WorkflowExecutorError instead of returning a graceful
error outcome. Also rename local variable interruption → execution.

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

Add { skipped: true } to the executionResult union type, removing the
unsafe `as unknown as` cast when the user rejects the update.

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

Keep pendingUpdate in the saved execution data after both confirm and
reject — useful for audit trail (what was proposed vs what happened).

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

Pass userInput as parameter to handleConfirmation() where it is already
narrowed, removing the `as { confirmed: boolean }` cast. Also move
resolveFieldName inside try-catch blocks so WorkflowExecutorError is
properly caught and returned as a status: 'error' outcome.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…prove coverage

- Extract resolveAndUpdate to deduplicate resolve+update+persist logic
- Extract buildOutcomeResult/buildSuccessResult/buildErrorResult helpers
- Move getCollectionSchema inside try-catch in confirmation flow
- Rename executionParams.fieldName to fieldDisplayName for consistency
- Add tests for resolveFieldName failure in both branches
- Add test for relationship field exclusion from update tool schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dErrorResult wrappers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ment executionResult

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…field resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move findField to BaseStepExecutor with displayName priority over fieldName
- Move buildOutcomeResult to BaseStepExecutor, use error !== undefined check
- Fix resolveAndUpdate try-catch scope (saveStepExecution outside try block)
- Fix ConditionStepExecutor catch-all to only catch WorkflowExecutorError
- Add pendingUpdate visibility in step summary
- Remove findField from types/record.ts and barrel export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Condition steps handle BPMN routing, record-task steps operate on client
data. The previous "ai-task" name was misleading since both step types
use AI. Rename to "record-task" to reflect the actual distinction.

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

Drop recordSourceStepId and remoteToolsSourceId — both were declared but
never read. allowedTools is kept as the orchestrator-provided tool filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces StepType.ToolTask ('tool-task') and ToolTaskStepDefinition for
steps where the AI freely selects and executes a tool from allowedTools.
Moves allowedTools out of RecordTaskStepDefinition where it didn't belong.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…AndUpdate arity

- Extract UpdateTarget interface {selectedRecordRef, fieldDisplayName, value} so
  resolveAndUpdate takes 2 params instead of 4 (addresses qlty reviewer comment)
- Collapse 3 uninitialized let declarations in handleFirstCall into one target object
- Fix missing runId in getAvailableRecordRefs and update-record saveStepExecution calls
- Strengthen test assertions to include runId as first arg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecutor

Code changes:
- Add JSDoc on buildOutcomeResult warning it is for record-task executors only
- Add runtime guard on userInput.type in handleConfirmation (WorkflowExecutorError
  if an unexpected type is received, guarding against future UserInput union extension)

New tests:
- buildStepSummary: pendingUpdate branch coverage (update-record step with
  pendingUpdate but no executionParams emits Pending: in AI context)
- handleConfirmation: stepIndex mismatch and missing pendingUpdate cases
- stepOutcome shape: type/stepId/stepIndex asserted on happy path
- unexpected userInput type: runtime guard verified
- findField fieldName fallback: update succeeds when AI returns raw fieldName
- schema caching: getCollectionSchema called once per collection in Branch B
- RunStore error propagation: all four saveStepExecution/getStepExecutions call sites

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pletion→automaticExecution

- StepType.ToolTask → StepType.McpTask (wire value: 'tool-task' → 'mcp-task')
- ToolTaskStepDefinition → McpTaskStepDefinition
- mcpServerId: string[] → string (single ID)
- automaticCompletion → automaticExecution on both RecordTaskStepDefinition and McpTaskStepDefinition
- Remove TODO rename comments
- Update all tests accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…record-1'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the UserInput discriminated union with a plain boolean field
userConfirmed on ExecutionContext and PendingStepExecution. Since the
only user input type in practice is a boolean confirmation, the union
wrapper adds complexity with no benefit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion flow

Implements TriggerActionStepExecutor following the UpdateRecordStepExecutor
pattern (branches A/B/C, confirmation flow, automaticExecution).

- Add TriggerActionStepExecutionData type with executionParams (actionDisplayName
  + actionName), executionResult ({ success } | { skipped }), and pendingAction
- Add NoActionsError for collections with no actions
- Implement selectAction via AI tool with displayName enum and technical name hints
- resolveAndExecute stores the technical actionName in executionParams for
  traceability; action result discarded per privacy constraint
- Fix buildStepSummary in BaseStepExecutor to include trigger-action pendingAction
  in prior-step AI context (parity with update-record pendingUpdate)
- Export TriggerActionStepExecutor, TriggerActionStepExecutionData, NoActionsError

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t and pendingAction

Resolve actionName once in handleFirstCall and store it in pendingAction,
so resolveAndExecute receives it directly via ActionTarget without re-fetching
the schema. Rename TriggerTarget → ActionTarget for consistency with English
naming conventions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…f shapes to { name, displayName }

- Extract ActionRef { name, displayName } from inline types in TriggerActionStepExecutionData
- Extract FieldRef { name, displayName } replacing FieldReadBase in ReadRecord types
- UpdateRecordStepExecutionData executionParams/pendingUpdate now use FieldRef & { value }
- Rename actionDisplayName/actionName → displayName/name, fieldDisplayName/fieldName → displayName/name
- Move resolveFieldName to handleFirstCall (no re-resolution in resolveAndUpdate)
- Add missing tests: resolveActionName not-found path, saveStepExecution not-called assertions, trigger-action pendingAction in buildStepSummary
- Export ActionRef, FieldRef, NoActionsError from index; update CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines 2 to 6
export type {
ConditionStepDefinition,
AiTaskStepDefinition,
RecordTaskStepDefinition,
StepDefinition,
} from './types/step-definition';
Copy link

Choose a reason for hiding this comment

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

🟡 Medium src/index.ts:2

McpTaskStepDefinition is defined and included in the StepDefinition union, but not exported from index.ts. Consumers cannot import this type directly to narrow or type-guard MCP task steps. Consider adding it to the named type exports.

 export type {
   ConditionStepDefinition,
   RecordTaskStepDefinition,
   StepDefinition,
 } from './types/step-definition';
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/index.ts around lines 2-6:

`McpTaskStepDefinition` is defined and included in the `StepDefinition` union, but not exported from `index.ts`. Consumers cannot import this type directly to narrow or type-guard MCP task steps. Consider adding it to the named type exports.

Evidence trail:
packages/workflow-executor/src/index.ts (lines 2-6): exports `ConditionStepDefinition`, `RecordTaskStepDefinition`, `StepDefinition` but not `McpTaskStepDefinition`

packages/workflow-executor/src/types/step-definition.ts (lines 30-35): defines `McpTaskStepDefinition`

packages/workflow-executor/src/types/step-definition.ts (lines 37-40): `StepDefinition` union includes `McpTaskStepDefinition`

@qltysh
Copy link

qltysh bot commented Mar 20, 2026

Qlty

Coverage Impact

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

Modified Files with Diff Coverage (9)

RatingFile% DiffUncovered Line #s
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/types/step-definition.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New file Coverage rating: A
...s/workflow-executor/src/executors/record-task-step-executor.ts100.0%
New file Coverage rating: A
...workflow-executor/src/executors/update-record-step-executor.ts100.0%
New file Coverage rating: A
...-executor/src/executors/trigger-record-action-step-executor.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/errors.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Total100.0%
🚦 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.

alban bertolini and others added 3 commits March 20, 2026 12:50
…store display names in executionParams

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rams for consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-executor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
alban bertolini and others added 3 commits March 20, 2026 14:20
…y" wording

Use fieldNames instead of fieldDisplayNames and actionName instead of
displayName in tool schemas exposed to the AI, to avoid confusion between
property names and their semantics (values remain display names).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ize pending state

- Make buildOutcomeResult abstract on BaseStepExecutor; each branch owns its outcome shape
- Add RecordTaskStepExecutor intermediate class implementing buildOutcomeResult for 'record-task'
- Add pendingData to BaseStepExecutionData, replacing pendingUpdate/pendingAction per-type fields; each executor sets it when saving, base class reads it directly with no type discrimination
- Rename TriggerActionStepExecutor → TriggerRecordActionStepExecutor for naming consistency with ReadRecord/UpdateRecord

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…idate error handling

- Introduce doExecute() as the abstract hook; execute() in the base class wraps it
  with the single try-catch that converts WorkflowExecutorErrors to error outcomes
  and rethrows infrastructure errors — removing all try-catch boilerplate from subclasses
- Inline the former buildErrorOutcomeOrThrow helper directly into execute(), it no
  longer needs to be a named method
- Add StepPersistenceError (extends WorkflowExecutorError) for post-side-effect
  persistence failures; these are now consistently converted to error outcomes
- Extract handleConfirmationFlow into RecordTaskStepExecutor to DRY the
  confirmation pattern shared by update-record and trigger-record-action
- Split the !execution?.pendingData guard into two distinct WorkflowExecutorErrors
  for clearer debug messages
- Export BaseStepStatus from step-outcome; remove pendingData from BaseStepExecutionData
  (declared only on the concrete types that need it)
- Fix extractToolCallArgs to throw MalformedToolCallError when args is null/undefined

Co-Authored-By: Claude Sonnet 4.6 <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.

1 participant