@@ -13793,6 +13793,45 @@ const step18: MigrationStep = {
1379313793 + 'number or boolean slot of any other builtin it fails at its first run exactly as it did, so write a '
1379413794 + 'literal there.',
1379513795 },
13796+ // A flow CEL expression's record root is the record the run was handed, or
13797+ // unbound. The flow engine used to bind record to the run's own variables
13798+ // whenever the run held no record, so record.assignee silently read a flow
13799+ // variable named assignee. That spelling now fails the run with an unknown-
13800+ // variable fault on record. Semantic-only: whether a run holds a record depends
13801+ // on the entrances that start the flow (a trigger, an action, a parent flow, a
13802+ // map item), which are not visible from the flow alone, so no D2 conversion can
13803+ // rewrite the source mechanically.
13804+ {
13805+ id: 'flow-cel-record-variables-alias-retired',
13806+ // No backticks in `surface` — build-upgrade-guide renders it inside a code
13807+ // span already, and a nested backtick would close it.
13808+ surface:
13809+ 'flows[].nodes[].config.condition, flows[].edges[].condition, a decision node\'s conditions[].expression, a '
13810+ + 'screen node\'s fields[].visibleWhen, and the CEL value envelopes of an assignment node\'s assignments and of '
13811+ + 'a create_record or update_record node\'s fields — a CEL source reading record.X, or record bare, on a run '
13812+ + 'that holds no record, where X names a flow variable',
13813+ replacement:
13814+ 'the variable by its name, or through the vars namespace: record.assignee becomes assignee, or vars.assignee. '
13815+ + 'A record.X read on a run that was handed a record is unchanged, and reads that record\'s field X',
13816+ reason:
13817+ 'Flow CEL binds record to the record the run was handed: a record-change trigger\'s row, a time-relative '
13818+ + 'sweep\'s row, the inbound hook\'s request body, a flow action\'s record (the loaded row, or an empty record '
13819+ + 'carrying at most the id it was given), a subflow or map parent\'s record, or a map item that carries an id. '
13820+ + 'A flow can also bind a variable named record itself. A run with none of these, one started through the REST '
13821+ + 'trigger route, a declared endpoint or a cron schedule, or a child of such a run, used to see record bound to '
13822+ + 'its own variables map, so record.X answered a flow variable named X instead of failing: a second spelling of '
13823+ + 'every variable, which hid the author\'s mistake. Such a run now fails the expression with an unknown-variable '
13824+ + 'fault on record, carrying the source, as every other unbound root does. No D2 conversion exists: whether a '
13825+ + 'run holds a record depends on the entrances that start the flow, which the flow alone does not show, so '
13826+ + 'only the author can tell a variable read from a record field read. Measured at the change: 0 flows in this '
13827+ + 'repository\'s example apps, platform objects and dogfood suites, and 0 in objectstack-ai/hotcrm, read record '
13828+ + 'with no record entrance.',
13829+ acceptanceCriteria:
13830+ 'For each flow whose CEL reads record, list the entrances that start it. Where any of them hands no record, '
13831+ + 'replace each record.X that names a flow variable with the variable\'s name or vars.X. Re-run each flow path '
13832+ + 'started with no record and confirm every expression evaluates instead of failing with an unknown-variable '
13833+ + 'fault on record; re-run a path started with a record and confirm record.X still reads that record\'s field.',
13834+ },
1379613835 // A flow CEL expression is refused when it reads a root the flow does not bind.
1379713836 // Formulas, row-level security and the client bind the run's user as user,
1379813837 // ctx.user and os.user as well as current_user; flow CEL binds current_user only,
0 commit comments