feat(workflow-executor): serialize recordId as pipe string at front/orchestrator boundaries#1594
Conversation
|
Coverage Impact Unable to calculate total coverage change because base branch coverage was not found. Modified Files with Diff Coverage (6) 🛟 Help
|
778bbfd to
7d9cbf4
Compare
…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>
7d9cbf4 to
6847d82
Compare
| // 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(), |
There was a problem hiding this comment.
🟡 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/**`.
… 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>

Summary
serializeRecordId/deserializeRecordIdutility insrc/adapters/record-id-serializer.tsrun-to-available-step-mapper.ts: splits pipe-separatedselectedRecordIdfrom the orchestrator into a proper array ('id1|id2'→['id1', 'id2'])executor-http-server.tsviasrc/http/step-serializer.ts: convertsrecordIdarrays to pipe strings inGET /runs/:runIdresponses (['id1', 'id2']→'id1|id2')pending-data-validators.ts: acceptsselectedRecordIdas a pipe string from POST trigger body and transforms to arrayTest plan
record-id-serializer.test.ts— unit tests for the two utility functionsrun-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 stepspending-data-validators.test.ts— deserialization tests for pipe-stringselectedRecordId788 tests passing.
fixes PRD-214
🤖 Generated with Claude Code
Note
Serialize
RecordIdas a pipe-separated string at workflow executor HTTP and orchestrator boundariesserializeRecordIdanddeserializeRecordIdutilities inrecord-id-serializer.tsto convert betweenRecordIdarrays and pipe-joined strings (e.g.'a|b').GET /runs/:runIdresponse now serializes allrecordIdfields (selected record refs, load-related-record candidates, suggested records, and execution results) to pipe-separated strings viastep-serializer.ts.POST /runs/:runId/triggernow expectsselectedRecordIdas a pipe-separated string and deserializes it into an array, validated inpending-data-validators.ts.load-related-record,read-record,update-record,trigger-action) now pass the fullRecordIdarray to activity log args instead of only the first segment; the activity log adapter serializes it to a pipe string before sending.toAvailableStepExecutioninrun-to-available-step-mapper.tsnow throwsInvalidStepDefinitionErrorwhenselectedRecordIdis missing or empty, and deserializes the pipe string into an array.Macroscope summarized eabb6ec.