You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #3426. PR #3472 closed the formula half of {record.<path>} template resolution (via the #3445 re-read) and added a build-time warning for unresolvable paths. The remaining piece — single-hop lookup traversal like {record.account.name} — is deferred here because it hinges on a design decision, not plumbing.
Problem
In a record-change flow, a node template {record.account.name} (where account is a lookup) renders as an empty string. The seeded flow record carries the relation as a scalar foreign-key id, not an expanded object, so the template walk .name on a string id yields nothing — silently. Same for a notify node's title/body, start conditions, etc.
Scoping — the plumbing is easy (no spec/engine/automation changes)
FlowTriggerBinding.config already carries the start node's config by reference (service-automation/src/engine.tsresolveTriggerBinding), so a config.expand opt-in surfaces on the binding with no automation-engine change.
IDataEngine.findOne already accepts an expand: Record<string, QueryAST> map, and expandRelatedRecords (objectql/src/engine.ts) infers the target object from the field's reference — passing expand: { account: {} } is enough; no nested object needed.
The flow start node's config is an open z.record, so an expand key validates today with no spec schema change.
The blocker — a read-identity / authorization decision
The trigger-record-change re-read that hydrates the record runs as an elevated system principal (so formula virtuals aren't FLS-masked). If we expanded a relation there, the expand sub-read would also run as system — bypassing the triggering user's RLS/FLS on the referenced object. For a runAs:'user' flow, {record.account.secret} in a notify template would then leak fields the user can't otherwise see.
Reading the expand as the user is not feasible in the trigger: the ObjectQL hook session carries only userId, not the user's resolved positions/permissions — the automation engine resolves those only at flow-run setup (see #3356). So the trigger structurally lacks the identity needed to scope a safe expand.
Proposed direction (needs a small ADR)
Do the lookup expansion inside the automation engine's flow run, which already resolves the triggering user's full grants for runAs:'user', rather than in the trigger's system re-read. Alternatively, a lazy template-time resolve that uses the flow's execution identity. Either keeps the RLS/FLS check intact.
Design points to settle:
Where expand is declared (flow/start-node config.expand: string[] vs per-node) and its shape.
Workaround: add a formula field on the source object that projects the related value (e.g. account_name) and reference that, or recompose from local scalar columns.
Acceptance
{record.account.name} resolves in flow templates/conditions under runAs:'user'without exposing fields the triggering user could not read directly.
Follow-up to #3426. PR #3472 closed the formula half of
{record.<path>}template resolution (via the #3445 re-read) and added a build-time warning for unresolvable paths. The remaining piece — single-hop lookup traversal like{record.account.name}— is deferred here because it hinges on a design decision, not plumbing.Problem
In a record-change flow, a node template
{record.account.name}(whereaccountis a lookup) renders as an empty string. The seeded flow record carries the relation as a scalar foreign-key id, not an expanded object, so the template walk.nameon a string id yields nothing — silently. Same for anotifynode'stitle/body, start conditions, etc.Scoping — the plumbing is easy (no spec/engine/automation changes)
FlowTriggerBinding.configalready carries the start node's config by reference (service-automation/src/engine.tsresolveTriggerBinding), so aconfig.expandopt-in surfaces on the binding with no automation-engine change.IDataEngine.findOnealready accepts anexpand: Record<string, QueryAST>map, andexpandRelatedRecords(objectql/src/engine.ts) infers the target object from the field'sreference— passingexpand: { account: {} }is enough; no nestedobjectneeded.configis an openz.record, so anexpandkey validates today with no spec schema change.The blocker — a read-identity / authorization decision
The
trigger-record-changere-read that hydrates the record runs as an elevated system principal (so formula virtuals aren't FLS-masked). If we expanded a relation there, the expand sub-read would also run as system — bypassing the triggering user's RLS/FLS on the referenced object. For arunAs:'user'flow,{record.account.secret}in a notify template would then leak fields the user can't otherwise see.Reading the expand as the user is not feasible in the trigger: the ObjectQL hook session carries only
userId, not the user's resolved positions/permissions — the automation engine resolves those only at flow-run setup (see #3356). So the trigger structurally lacks the identity needed to scope a safe expand.Proposed direction (needs a small ADR)
Do the lookup expansion inside the automation engine's flow run, which already resolves the triggering user's full grants for
runAs:'user', rather than in the trigger's system re-read. Alternatively, a lazy template-time resolve that uses the flow's execution identity. Either keeps the RLS/FLS check intact.Design points to settle:
expandis declared (flow/start-nodeconfig.expand: string[]vs per-node) and its shape.{record.account}(bare id) and [P2] record-change context does not hydrate multi-lookup (junction) fields #1872 multi-lookup arrays must stay untouched for non-expanded relations.runAs:'user'; system only forrunAs:'system').Meanwhile
os validatenow warns on these paths (flow-template-lookup-traversal, added in PR fix(#3426): build-time warning for unresolvable flow templates + guard the formula re-read #3472), so authors get a design-time signal instead of a silent blank.formulafield on the source object that projects the related value (e.g.account_name) and reference that, or recompose from local scalar columns.Acceptance
{record.account.name}resolves in flow templates/conditions underrunAs:'user'without exposing fields the triggering user could not read directly.{record.<lookup>}ids andmultiplelookup arrays ([P2] record-change context does not hydrate multi-lookup (junction) fields #1872) are unaffected for relations not expanded.flow-template-lookup-traversallint warning is suppressed for a relation once expansion is declared.