Flow vs. FLS: Non-Reparentable Relationship Fields

In my previous post, we explored the unexpected consequences of enforced User Mode for database operations in Apex when processing records with a non-reparentable relationship.
We know how it works on the Apex side - a simple update statement fails and throws the following exception:
System.DmlException: Operation failed due to fields being inaccessible on Sobject OrderItem, check errors on Exception or Result!
What about Flows then? Are similar update actions processed differently there?
Get and Update Record elements
Flow executed with only Flow elements does not have that issue, despite OrderId clearly being specified in the call. Flow was set to run in User or System Context (User or System Context - Depends on How the Flow Is Launched).

So, it indicates that Flow’s context executes slightly differently than Apex.
Using an Invocable action to update records
Let’s try using Apex’s Invocable Action to update the records and see whether the exception is thrown.
public inherited sharing class UpdateOrderItemInvocable {
@InvocableMethod(
Label='Update Order Item'
Description='Updates the provided OrderItem record'
Category='Order'
)
public static void execute(List<OrderItem> orderItems) {
update orderItems; // API 67.0 enforces USER MODE here
}
}When running the Flow, in Invocable Action the “fields being inaccessible” error occurs. However, more fields were passed into the Apex than in the Flow’s update:

Unfortunately, it is due to using the “Automatically store all fields” option in the Get Records block. Other options to store a database record are not as convenient, as they do not let you refer to an Order’s (parent’s) fields.
We can try to sanitize the record on the Flow side. It can be easily done with the Transform element.
When we intentionally pass the OrderId, an error is still thrown.

When we don’t pass the OrderId field in the Transform block, the Apex update runs successfully.

Using an Invocable action to query records
This time, let’s check what will happen when the query is made on the Apex side instead, while the update is invoked from the Flow.
public inherited sharing class GetOrderItemInvocable {
@InvocableMethod(
Label='Get Order Item with Order Status'
Description='Queries an OrderItem'
Category='Order'
)
public static List<OrderItem> execute() {
return [SELECT Id, Description, Order.Status FROM OrderItem];
}
}This time, Flow executes without an error again.

My conclusion at that point is that Flow’s Update element does not enforce the “Edit” Field Permission on the non-updateable OrderId field, or maybe implicitly compares whether the field state was changed. Nevertheless, it’s clearly working differently than in Apex.
Explicitly changing the Parent ID with Update Record elements
Let’s then try to make a direct change to the OrderId field!
A good start would be to use a totally fake OrderId: 801000000000000000
The results are… unexpected, as the update succeeds.

I’ve run the SOQL query on the record afterwards, and of course, the invalid fake ID was not specified on the OrderItem. Strange.
When setting the OrderID to null, the update also passes.

Strange. I’ve attempted to put another valid OrderId in that field, and it also succeeded.
So, to confirm it further, I’ve created two Custom Objects with Master-Detail between them, and disabled reparenting.
Next, I ran the Flow with this new object, setting the existing ParentId to null, and… the Flow succeeded. Again.

Conclusion
Conclusion - Flows do not enforce Field-Level Security for the non-reparentable relation fields. For me, that’s highly unexpected behaviour. Normally, when the User is missing the Field-Level Security, Flow throws an exception of “The flow tried to update these records: <recordId>. This error occurred: INVALID_FIELD_FOR_INSERT_UPDATE: Unable to create/update fields: <fieldname>. Please check the security settings of this field and verify that it is read/write for your profile or permission set.”.
To be a bit more pedantic - the non-reparentable standard relationships and Master-Detail do not have modifiable Field-Level Security, so the above behaviour is more of a database integrity constraint. It may not be that Flows stop enforcing the FLS, but they silently ignore the non-editable fields from the payload. Even when we put only the OrderId in the Update block and run it, record in database will not change despite Flow resulting in success.
Nevertheless, the point is that Flow works differently than Apex in this area, as Apex throws an error regardless of whether the field is reparentable or not.
Differences between Apex and Flow
When working with non-reparentable relations, these differences are present:
- Updating record containing ParentId
- Apex (API 67.0+): System.DmlException: Operation failed due to fields being inaccessible on Sobject OrderItem. Exception thrown on update, update prevented.
- Flow: Update passes, ParentId not updated.
- Setting existing ParentId to NULL
- Apex: System.SObjectException: Field is not writable.
- Flow: No error thrown, ParentId not updated.
- Setting existing ParentId to another ParentId
- Apex: System.SObjectException: Field is not writable.
- Flow: No error thrown, ParentId not updated.
- Sanitizing the record
- Apex: Requires a custom Apex approach.
- Flow: Done with a simple Transform or nullification in Assignment element.
