fix(workflow-executor): follow a reference smart field whose value is the related id - #1884
Open
Scra3 wants to merge 2 commits into
Open
fix(workflow-executor): follow a reference smart field whose value is the related id#1884Scra3 wants to merge 2 commits into
Scra3 wants to merge 2 commits into
Conversation
… the related id forest-rails and forest-express serialize a reference smart field as a plain JSON:API attribute, so the value is the related record id and there is no linkage object to unpack. getSingleRelatedData returned null on those, which reads as "no related record" instead of loading it. The scalar branch reads the target by that id through the same port method, so the step executor stays unaware of which shape the agent emitted. An orphan id still raises RecordNotFoundError: that is a data inconsistency, not an absent relation. Ships before the server change that serves these fields as BelongsTo, so no executor ever sees a relation it cannot follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (1)
🛟 Help
|
… single-key values Review of the paired server change surfaced two holes in the scalar branch. An empty string passed the guard. `object.card&.id.to_s` is the idiomatic Ruby getter and answers "" for an unset association, which serialized to an id-less by-id URL that agents route to the index action instead — a list where a record was expected. It now takes the same path as a missing linkage and returns null. The pipe split was unconditional. That packing is the agent's own convention for a composite key, but a smart field's value is written by client code, so an id that legitimately contains "a|b" was torn in two against a single-key target. It now splits only when the target key is composite, mirroring getRelatedData. Also corrects the comment: forest-express serializes every reference field as a JSON:API relationship, and so does forest-rails' `belongs_to` DSL from the very same apimap shape. Only `field ... reference:` yields an attribute, which is why the branch reads the runtime shape rather than the schema. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 new issue
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Context
A client's
load-related-recordstep could not follow thecardrelation of theirCardClaim__Claimcollection. Their agent is forest-rails, wherecardis a reference smart field: it carries areferenceand norelationship.This PR fixes the executor half. The server half is forestadmin-server#8482, and this one ships first — see Rollout.
The problem
getSingleRelatedDatafollows an xToOne relation by reading the parent with a<relation>@@@<field>projection and unpacking the JSON:API linkage. But a forest-rails smart field declared withfield ... reference:registers as a plain attribute (lib/forest_liana/collection.rb→attribute(name, &compute_value)), so the projected value is the related id itself, a bare string.linkage?.idwasundefined, the method returnednull, and the step concluded "no related record" instead of loading it.That silence is why this ships before the server change: once the server serves these fields as
BelongsTo, an executor without this fix turns a loud configuration error into a quiet false negative.Why a runtime shape check rather than a schema branch
The same apimap shape can serialize either way, so the schema cannot tell them apart:
field ... reference:type: 'String', norelationshipbelongs_to ... reference:has_one(include_data: true)→ linkagereferencefieldBranching on
typeof raw !== 'object'sorts them automatically, which is why the fix is small and why forest-express needs no special case.The change
One branch in the adapter, behind the same port method, so the step executor stays unaware of which shape the agent emitted:
getRecordon the related collection;null,undefinedand''keep the linkage path and still returnnull. The empty string matters:object.card&.id.to_sis the idiomatic Ruby getter and answers""for an unset association, which would have serialized to an id-less by-id URL that agents route to the index action;a|bmust survive intact;RecordNotFoundErrorsurface. Deliberate: the value is present, so the relation is set and the target simply cannot be read — that is a data inconsistency, not an absent relation.Tests
7 in
getSingleRelatedData: scalar read, callerfieldsforwarding, composite split, single-key pipe preserved, empty string, null, plus the untouched linkage cases. One step-level test that a pinned reference-onlyBelongsTois followed, asserting the target collection and not just the relation name.Full package suite: 1703 passed, 0 failed. Diff coverage 100%.
Rollout
Ship and release this before forestadmin-server#8482. In the reverse order, an executor without this fix sees a
BelongsTowhose value is a scalar, reads no linkage, and silently skips: for a fully-automated step the run goes green with no record.Node clients that embed the executor stay in that window until they upgrade — worth a line in the agent release note.
Definition of Done
General
Security
getRecordpath as every other record read, with the caller's own scopes; no new surface🤖 Generated with Claude Code