Skip to content

feat(workflow-executor): serialize recordId as pipe string at front/orchestrator boundaries#1594

Open
Scra3 wants to merge 7 commits into
feat/prd-214-server-step-mapperfrom
fix/workflow-executor-composite-key-pipe-serialization
Open

feat(workflow-executor): serialize recordId as pipe string at front/orchestrator boundaries#1594
Scra3 wants to merge 7 commits into
feat/prd-214-server-step-mapperfrom
fix/workflow-executor-composite-key-pipe-serialization

Conversation

@Scra3
Copy link
Copy Markdown
Member

@Scra3 Scra3 commented May 26, 2026

Summary

  • Adds serializeRecordId / deserializeRecordId utility in src/adapters/record-id-serializer.ts
  • Applies deserialization in run-to-available-step-mapper.ts: splits pipe-separated selectedRecordId from the orchestrator into a proper array ('id1|id2'['id1', 'id2'])
  • Applies serialization in executor-http-server.ts via src/http/step-serializer.ts: converts recordId arrays to pipe strings in GET /runs/:runId responses (['id1', 'id2']'id1|id2')
  • Applies deserialization in pending-data-validators.ts: accepts selectedRecordId as a pipe string from POST trigger body and transforms to array

Test plan

  • record-id-serializer.test.ts — unit tests for the two utility functions
  • run-to-available-step-mapper.test.ts — new test for composite key 'pk1|pk2'['pk1', 'pk2']
  • executor-http-server.test.ts — serialization tests for update-record and load-related-record steps
  • pending-data-validators.test.ts — deserialization tests for pipe-string selectedRecordId

788 tests passing.

fixes PRD-214

🤖 Generated with Claude Code

Note

Serialize RecordId as a pipe-separated string at workflow executor HTTP and orchestrator boundaries

  • Introduces serializeRecordId and deserializeRecordId utilities in record-id-serializer.ts to convert between RecordId arrays and pipe-joined strings (e.g. 'a|b').
  • The GET /runs/:runId response now serializes all recordId fields (selected record refs, load-related-record candidates, suggested records, and execution results) to pipe-separated strings via step-serializer.ts.
  • POST /runs/:runId/trigger now expects selectedRecordId as a pipe-separated string and deserializes it into an array, validated in pending-data-validators.ts.
  • Step executors (load-related-record, read-record, update-record, trigger-action) now pass the full RecordId array to activity log args instead of only the first segment; the activity log adapter serializes it to a pipe string before sending.
  • toAvailableStepExecution in run-to-available-step-mapper.ts now throws InvalidStepDefinitionError when selectedRecordId is missing or empty, and deserializes the pipe string into an array.
  • Behavioral Change: composite record IDs previously truncated to their first segment are now fully preserved across the HTTP boundary.

Macroscope summarized eabb6ec.

@linear
Copy link
Copy Markdown

linear Bot commented May 26, 2026

PRD-214

PRD-399

@qltysh
Copy link
Copy Markdown

qltysh Bot commented May 26, 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 Coverage rating: A
...-executor/src/adapters/forestadmin-client-activity-log-port.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/http/pending-data-validators.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/http/executor-http-server.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/adapters/record-id-serializer.ts100.0%
New Coverage rating: A
...workflow-executor/src/adapters/run-to-available-step-mapper.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/http/step-serializer.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.

@Scra3 Scra3 force-pushed the fix/workflow-executor-composite-key-pipe-serialization branch from 778bbfd to 7d9cbf4 Compare May 26, 2026 13:12
Comment thread packages/workflow-executor/src/adapters/record-id-serializer.ts Outdated
@Scra3
Copy link
Copy Markdown
Member Author

Scra3 commented May 26, 2026

alban bertolini and others added 4 commits June 3, 2026 16:33
…rchestrator boundaries

Composite primary keys require multiple ID segments. The frontend cannot
handle JSON arrays for IDs, so recordId arrays are serialized to a
pipe-separated string (e.g. ['id1', 'id2'] ↔ 'id1|id2') at the three
communication boundaries:

- Orchestrator → Executor: deserialize selectedRecordId string in the run mapper
- Executor → Front: serialize recordId arrays in GET /runs/:runId response
- Front → Executor: deserialize selectedRecordId pipe string in POST trigger body

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

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

incomingPendingData.selectedRecordId now arrives as a pipe string (e.g. '42') from the front,
parsed by the Zod schema to an array. Update test inputs and recordId assertions accordingly.

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

After rebase: LoadRelatedRecordPendingData no longer has selectedRecordId — it
carries availableRecordIds[] + suggestedRecord. Serialize those recordIds to the
pipe-string wire format instead, and update the http-server/validators tests
(fieldName + string selectedRecordId input).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/workflow-executor-composite-key-pipe-serialization branch from 7d9cbf4 to 6847d82 Compare June 3, 2026 14:39
// User may override the AI-selected record; pipe-separated string (e.g. 'id1|id2'),
// deserialized to an id array. Required when confirming with a relation override —
// the original record ID belongs to a different collection and cannot be reused.
selectedRecordId: z.string().min(1).transform(deserializeRecordId).optional(),
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 http/pending-data-validators.ts:47

deserializeRecordId returns string[] but is typed as Array<string | number>, and downstream code may perform strict equality checks like selectedId === record.id where record.id is a number. The comparison fails because "123" !== 123, breaking record matching. Consider updating deserializeRecordId to coerce numeric-looking strings to numbers so the runtime values match the declared type and existing numeric IDs.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/http/pending-data-validators.ts around line 47:

`deserializeRecordId` returns `string[]` but is typed as `Array<string | number>`, and downstream code may perform strict equality checks like `selectedId === record.id` where `record.id` is a number. The comparison fails because `"123" !== 123`, breaking record matching. Consider updating `deserializeRecordId` to coerce numeric-looking strings to numbers so the runtime values match the declared type and existing numeric IDs.

Evidence trail:
packages/workflow-executor/src/adapters/record-id-serializer.ts:5-6 — `deserializeRecordId` calls `value.split('|')` (always returns `string[]`) with return type `Array<string | number>`. packages/workflow-executor/src/adapters/record-id-serializer.ts:1-3 — `serializeRecordId` takes `Array<string | number>` and `.map(String).join('|')`, showing the round-trip is not type-preserving. packages/workflow-executor/src/http/pending-data-validators.ts:47 — Zod transform uses `deserializeRecordId`. packages/workflow-executor/src/executors/load-related-record-step-executor.ts:321-340 — downstream usage assigns result to `RecordRef.recordId` typed as `Array<string | number>` but never compares via `===`. git_grep for `recordId.*(find|includes|some|indexOf|===)` returned zero matches in `packages/workflow-executor/src/**`.

alban bertolini and others added 3 commits June 3, 2026 16:55
… logs

buildActivityLogArgs passed recordId[0], logging only the first segment of a
composite key. Pass the full recordId array and serialize it to the pipe wire
format inside the activity-log adapter (keeps the wire format out of executors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- mapper: guard empty/missing run.selectedRecordId with InvalidStepDefinitionError
  (was silently deserializing to a bogus [''] baseRecordRef), mirroring the
  collectionId/collectionName guards
- record-id-serializer: deserializeRecordId returns string[] (ids travel as opaque
  strings); document the reserved '|' delimiter + its accepted round-trip limitation;
  drop redundant .map(String)
- activity-log adapter: use args.recordId?.length (an empty array is no record id)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…string | number>

Replace the inline Array<string | number> recordId repetitions with a single
RecordId alias (derived from RecordRef['recordId']) in step-execution-data,
record-id-serializer, activity-log port, and the agent-client PK filter.

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.

1 participant