Skip to content

feat(workflow-executor): deterministic source record for Update Data steps (PRD-777)#1761

Open
EnkiP wants to merge 2 commits into
mainfrom
workflow/deterministic-update-data
Open

feat(workflow-executor): deterministic source record for Update Data steps (PRD-777)#1761
EnkiP wants to merge 2 commits into
mainfrom
workflow/deterministic-update-data

Conversation

@EnkiP

@EnkiP EnkiP commented Jul 15, 2026

Copy link
Copy Markdown
Member

Part of the deterministic Update Data step work.

fixes PRD-777

What

The update-record (Update Data) step can now resolve its source record deterministically from a build-time-configured stable BPMN step id (selectedRecordStepId), mirroring read-record (Get Data) and trigger-action — instead of always asking the AI to pick among the available records.

Changes

  • UpdateRecordStepDefinitionSchema.preRecordedArgs gains selectedRecordStepId (legacy selectedRecordStepIndex kept for back-compat; fieldName/value unchanged).
  • update-record-step-executor (handleFirstCall): when selectedRecordStepId is set, resolve via resolveSourceRecordRef (revise-safe; WORKFLOW_START_STEP_ID → base record). Falls back to legacy index / AI selection when unset.
  • Server contract (ServerWorkflowTaskUpdateData) + mapper forward preRecordedArgs for update-data.

Tests

  • Deterministic resolve skips the select-record AI round; WORKFLOW_START_STEP_ID → base record; unresolvable id → error.
  • Mapper forwards preRecordedArgs (selectedRecordStepId + fieldName + value).

Companion PRs: forestadmin-server (orchestrator), forestadmin (editor UI).

🤖 Generated with Claude Code

Note

Add deterministic source record resolution and pre-recorded field/value support to Update Data steps

  • Adds selectedRecordStepId to UpdateRecordStepDefinitionSchema and the ServerWorkflowTaskUpdateData interface as a stable alternative to the legacy index-based selectedRecordStepIndex for identifying the source record.
  • Extends UpdateRecordStepExecutor.handleFirstCall to support three configurations: both fieldName and value pre-recorded (use directly), only fieldName pre-recorded (ask AI for value via new selectValueForField method), or neither (ask AI for both).
  • Adds buildZodSchemaForField/buildZodSchemaForPrimitive forAiTool flag so boolean schemas bound into AI tools are plain z.boolean() (JSON Schema compatible) instead of using z.preprocess string coercion; coercion now happens after tool output.
  • Behavioral Change: AI tools now reject string booleans ("true"/"false") directly; coercion to native boolean occurs in the executor after the tool responds.

Macroscope summarized 8dad337.

@linear-code

linear-code Bot commented Jul 15, 2026

Copy link
Copy Markdown

PRD-777

@qltysh

qltysh Bot commented Jul 15, 2026

Copy link
Copy Markdown

3 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 6): mapTask 3

Comment on lines +190 to +191
const selectedRecordRef = preRecordedArgs?.selectedRecordStepId
? await this.resolveSourceRecordRef(preRecordedArgs.selectedRecordStepId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium executors/update-record-step-executor.ts:190

When preRecordedArgs.selectedRecordStepId is an empty string, handleFirstCall treats it as unset and silently falls back to AI/legacy record selection instead of calling resolveSourceRecordRef to resolve the configured ID. An Update Data step can update a different record than its deterministic configuration specifies. The truthiness check should be an explicit !== undefined check, like the load-related executor uses.

Suggested change
const selectedRecordRef = preRecordedArgs?.selectedRecordStepId
? await this.resolveSourceRecordRef(preRecordedArgs.selectedRecordStepId)
const selectedRecordRef = preRecordedArgs?.selectedRecordStepId !== undefined
? await this.resolveSourceRecordRef(preRecordedArgs.selectedRecordStepId)
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/workflow-executor/src/executors/update-record-step-executor.ts around lines 190-191:

When `preRecordedArgs.selectedRecordStepId` is an empty string, `handleFirstCall` treats it as unset and silently falls back to AI/legacy record selection instead of calling `resolveSourceRecordRef` to resolve the configured ID. An Update Data step can update a different record than its deterministic configuration specifies. The truthiness check should be an explicit `!== undefined` check, like the load-related executor uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@EnkiP EnkiP force-pushed the workflow/deterministic-update-data branch from 7fa69b5 to 2a2dbdd Compare July 16, 2026 07:54
@qltysh

qltysh Bot commented Jul 16, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (2)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
...workflow-executor/src/executors/update-record-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/adapters/step-definition-mapper.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.

…I-resolved value

The build-time config lets the Editor pin the field while leaving the value to
the runtime prompt. The 'fieldName and value must both be provided or both
omitted' guard wrongly rejected that, throwing InvalidPreRecordedArgsError. Now
a value-less fieldName pins the field and the AI resolves only the value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@EnkiP EnkiP force-pushed the workflow/deterministic-update-data branch from 6a1f30f to 8dad337 Compare July 16, 2026 15: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.

1 participant