Skip to content

fix(workflow): use technical names for field and action names#1625

Open
Scra3 wants to merge 6 commits into
feat/prd-214-server-step-mapperfrom
fix/prd-426-pre-recorded-args-technical-names
Open

fix(workflow): use technical names for field and action names#1625
Scra3 wants to merge 6 commits into
feat/prd-214-server-step-mapperfrom
fix/prd-426-pre-recorded-args-technical-names

Conversation

@Scra3
Copy link
Copy Markdown
Member

@Scra3 Scra3 commented Jun 4, 2026

Context

fixes PRD-426

The orchestrator→executor preRecordedArgs referenced fields/relations/actions by their displayName — a user-customizable label that is non-unique and can change. Using it as a wire reference breaks a pre-recorded step the moment an admin renames it. This PR makes the wire reference the technical name (the stable, unique column/relation/action id) instead.

Inspired by #1618 but scoped down: the displayName is still persisted and still drives the AI tool enums/prompts — it is just no longer used as a reference on the wire. Excludes the renderingId schema-cache fix (dedicated ticket).

Changes

Wire contract (step-definition.ts preRecordedArgs):

  • fieldDisplayNamesfieldNames (read-record)
  • fieldDisplayNamefieldName (update-record)
  • actionDisplayNameactionName (trigger-action)
  • relationDisplayNamerelationName (load-related-record)

displayName is now always resolved from the live schema at execution time (still persisted in the RunStore, still shown to the AI). This also fixes a latent bug where update-record and trigger-action persisted the inbound reference verbatim as displayName (resolveFieldNameresolveField, resolveActionNameresolveAction now derive { name, displayName } from the schema).

Exact technical-name resolution (review follow-up): pre-recorded (and front-confirmation) references resolve via a dedicated findFieldByTechnicalName / exact action lookup — findField's displayName-first + fuzzy matching is reserved for AI-returned display names. Without this, a technical name could resolve to a different field whose displayName collides with it (silent wrong-target write). Covered by a regression test.

⚠️ Breaking contract change — coordinated deployment required

The orchestrator (and the front, upstream) must send technical names in preRecordedArgs. The executor's step-definition schema strips unknown keys, so an old relationDisplayName would become undefined and silently fall back to the AI. This must ship in tandem with the orchestrator-side change.

Verification

  • yarn workspace @forestadmin/workflow-executor build
  • 883 tests pass (210 across the 4 record-step executors, incl. the displayName-collision regression) ✅
  • lint: 0 errors ✅

🤖 Generated with Claude Code

Note

Switch preRecordedArgs wire format to use technical names across all workflow step executors

  • Replaces display name fields (fieldDisplayNames, fieldDisplayName, actionDisplayName, relationDisplayName) with technical name fields (fieldNames, fieldName, actionName, relationName) in the preRecordedArgs schema for all step types.
  • Adds findFieldByTechnicalName and resolveAiFieldName helpers to RecordStepExecutor for exact resolution and AI display-name-to-technical-name mapping.
  • AI-returned display names are mapped to technical names via resolveAiFieldName before use; pre-recorded args now require exact technical names and throw typed errors (e.g. FieldNotFoundError, ActionNotFoundError) on mismatch.
  • Risk: Breaking change — any stored workflow definitions using the old display name keys in preRecordedArgs will fail schema validation.

Changes since #1625 opened

  • Modified mapping callback in ReadRecordStepExecutor.doExecute to return structured field objects [64ce331]
  • Renamed local variable in ReadRecordStepExecutor.doExecute method [64ce331]
  • Corrected undefined variable reference in ReadRecordStepExecutor.doExecute method within workflow-executor package [91fddd1]
  • Removed explanatory comments from workflow executor classes describing technical name resolution [9d64042]
  • Modified RecordStepExecutor.findFieldByTechnicalName to accept undefined as a parameter and return undefined immediately when no name is provided, and updated UpdateRecordStepExecutor.coerceOverride to pass undefined directly instead of defaulting to an empty string [53e7320]
📊 Macroscope summarized 64ce331. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@linear-code
Copy link
Copy Markdown

linear-code Bot commented Jun 4, 2026

PRD-426

@qltysh
Copy link
Copy Markdown

qltysh Bot commented Jun 4, 2026

All good ✅

@qltysh
Copy link
Copy Markdown

qltysh Bot commented Jun 4, 2026

Qlty


Coverage Impact

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

Modified Files with Diff Coverage (5)

RatingFile% DiffUncovered Line #s
New Coverage rating: A
...ow-executor/src/executors/load-related-record-step-executor.ts100.0%
New Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/executors/record-step-executor.ts100.0%
New Coverage rating: A
...workflow-executor/src/executors/update-record-step-executor.ts100.0%
New Coverage rating: A
...-executor/src/executors/trigger-record-action-step-executor.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.

The orchestrator→executor preRecordedArgs referenced fields/relations/
actions by displayName — a mutable, non-unique label that breaks a
pre-recorded step when an admin renames it. Reference them by their
stable technical name instead:

- fieldDisplayNames → fieldNames (read-record)
- fieldDisplayName  → fieldName  (update-record)
- actionDisplayName → actionName (trigger-action)
- relationDisplayName → relationName (load-related-record)

displayName is still persisted in the RunStore and still drives the AI
tool enums/prompts — it is now always resolved from the live schema at
execution time, never received as a wire reference. Fixes a latent bug
where update-record and trigger-action persisted the inbound reference
verbatim as displayName (resolveFieldName→resolveField,
resolveActionName→resolveAction now derive it from the schema).

Breaking contract change: the orchestrator must send technical names in
preRecordedArgs (coordinated deployment). Excludes the renderingId
schema-cache fix (dedicated ticket).

fixes PRD-426

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/prd-426-pre-recorded-args-technical-names branch 5 times, most recently from f051b0d to a35e26a Compare June 4, 2026 10:17
? { relationName: preRecordedArgs.relationDisplayName }
: await this.selectRelation(schema, step.prompt);
const target = this.buildTarget(schema, args.relationName, selectedRecordRef);
const recordedRelation = preRecordedArgs?.relationName;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

preRecordedArgs will be very useful for run-to-build move, when the orchestrator wants to inject inputs instead to use ai.

@Scra3 Scra3 force-pushed the fix/prd-426-pre-recorded-args-technical-names branch from a35e26a to 73ff4bc Compare June 4, 2026 10:28
…ayName collision)

Pre-recorded references arrive as technical names, but resolution went
through findField, which matches displayName first. A technical name
could then resolve to a *different* field/action whose displayName
collides with it — silently writing/triggering the wrong target.

Add a dedicated findFieldByTechnicalName (exact fieldName, no displayName,
no fuzzy) and an exact action lookup; the pre-recorded and front-
confirmation paths use them, while findField stays the lenient resolver
for AI-returned display names. Add a collision regression test.

Also correct the CLAUDE.md error-attribution: unresolvable names throw
*NotFoundError (read-record: NoResolvedFieldsError), not
InvalidPreRecordedArgsError (which guards arg shape only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/prd-426-pre-recorded-args-technical-names branch from 73ff4bc to 4e70f12 Compare June 4, 2026 11:53
@Scra3 Scra3 changed the title fix(workflow): use technical names in preRecordedArgs wire (PRD-426) fix(workflow): use technical names for field and action names Jun 4, 2026
Comment thread packages/workflow-executor/src/executors/read-record-step-executor.ts Outdated
Co-authored-by: Nicolas Bouliol <nicolas.bouliol@forestadmin.com>
Comment thread packages/workflow-executor/src/executors/read-record-step-executor.ts Outdated
alban bertolini and others added 3 commits June 5, 2026 22:42
…cord

The previous commit renamed the declaration but left three usages on the old
`selected` identifier (resolvedFieldNames map, NoResolvedFieldsError, and
formatFieldResults), which left it undefined and broke the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the "AI selects by displayName; map it back to the technical name…"
style comments added in this PR (selectFields, selectFieldAndValue,
selectAction, selectRelation, findFieldByTechnicalName). The fuzzy
resolveAiFieldName comment is kept (it documents non-obvious behavior).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <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.

2 participants