Skip to content

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

Open
Scra3 wants to merge 3 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 3 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 pipe-separated string at workflow executor HTTP boundaries

  • Adds serializeRecordId and deserializeRecordId to convert between string[] record ID arrays and pipe-delimited strings (e.g. "a|b|c").
  • The GET /runs/:runId response now serializes recordId arrays to pipe strings via serializeStepForWire for read-record, update-record, trigger-action, and load-related-record step types.
  • The POST /runs/:runId/trigger endpoint now accepts selectedRecordId as a pipe-separated string instead of an array, deserializing it internally via loadRelatedRecordPatchSchema.
  • run-to-available-step-mapper now deserializes run.selectedRecordId from a pipe string into an array when constructing baseRecordRef.recordId.
  • Behavioral Change: callers of the GET /runs/:runId and POST trigger endpoints must now send and expect recordId as a pipe-delimited string rather than an array.
📊 Macroscope summarized 7d9cbf4. 5 files reviewed, 2 issues evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@linear
Copy link
Copy Markdown

linear Bot commented May 26, 2026

PRD-214

@qltysh
Copy link
Copy Markdown

qltysh Bot commented May 26, 2026

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (5)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/http/pending-data-validators.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/http/executor-http-server.ts100.0%
Coverage rating: A Coverage rating: A
...workflow-executor/src/adapters/run-to-available-step-mapper.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/adapters/record-id-serializer.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.

alban bertolini and others added 3 commits May 26, 2026 15:08
…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>
@Scra3 Scra3 force-pushed the fix/workflow-executor-composite-key-pipe-serialization branch from 778bbfd to 7d9cbf4 Compare May 26, 2026 13:12
Comment on lines +1 to +7
export function serializeRecordId(recordId: Array<string | number>): string {
return recordId.map(String).join('|');
}

export function deserializeRecordId(value: string): Array<string | number> {
return value.split('|');
}
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 adapters/record-id-serializer.ts:1

serializeRecordId(["foo|bar", "baz"]) returns "foo|bar|baz", and deserializeRecordId("foo|bar|baz") returns ["foo", "bar", "baz"] — the original array structure is lost when any element contains |. Consider using an escaping scheme or structured format like JSON to preserve element boundaries.

-export function serializeRecordId(recordId: Array<string | number>): string {
-  return recordId.map(String).join('|');
-}
+export function serializeRecordId(recordId: Array<string | number>): string {
+  return JSON.stringify(recordId);
+}
 
 export function deserializeRecordId(value: string): Array<string | number> {
-  return value.split('|');
+  return JSON.parse(value);
 }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/record-id-serializer.ts around lines 1-7:

`serializeRecordId(["foo|bar", "baz"])` returns `"foo|bar|baz"`, and `deserializeRecordId("foo|bar|baz")` returns `["foo", "bar", "baz"]` — the original array structure is lost when any element contains `|`. Consider using an escaping scheme or structured format like JSON to preserve element boundaries.

Evidence trail:
packages/workflow-executor/src/adapters/record-id-serializer.ts (lines 1-7, REVIEWED_COMMIT) - the serialize/deserialize functions with no escaping or validation. packages/agent-client/src/record-id.ts (lines 19-25, REVIEWED_COMMIT) - the same codebase's other version that explicitly throws on `|` in elements. packages/agent-client/test/record-id.test.ts (line 37, REVIEWED_COMMIT) - test proving the guard: `expect(() => serializeRecordId(['1|abc', 2])).toThrow(...)`. packages/workflow-executor/src/http/pending-data-validators.ts (line 38, REVIEWED_COMMIT) - `deserializeRecordId` used on user-supplied input via Zod transform.

@Scra3
Copy link
Copy Markdown
Member Author

Scra3 commented May 26, 2026

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