This turned out to be my fault. While it's true that I was refreshing the parent record before saving the child record, I was doing so in parallel with a prior save of another child record of the same parent, and the earlier save was completing before the later refresh.
Swift:
recordA.save() // recordA.parent == parent 1
recordB.save() // recordB.parent == parent 1
Here's what happens in those two calls. lines 1 and 4 were executed synchronously, then respectively initiating the async calls on lines 2 and 5, which in turn initiated the async calls on lines 3 and 6.
The problem was that line 5 was completing before line 3, but line 3 was completing before line 6, so line 6 was failing because parent 1 had changed in the meantime (by line 3).
1: Save record A with parent 1
2: refresh parent 1
3: save record A
4: Save record B with parent 1
5: refresh parent 1
6: save record B
I was naive to initiate lines 1 and 3 together and fortunate that they failed quickly, since they might have worked well—until they didn't.
There are two solutions I can think of here:
Batch records A and B together in a single operation.Chain A > B so that B is initiated only after A completes.
The former solution is by far the preferable one, but the latter solution made more sense in my case, so the revised approach (which now works fine) looks like this:
In Swift, it looks much friendlier:
recordA.save() {
recordB.save() {
}
1: Save record A with parent 1
2: refresh parent 1
3: save record A
4: Save record B with parent 1
5: refresh parent 1
6: save record B
I still don't know what "chain PCS data" means, but in essence it seems to indicate that the records you're attempting to modify have changed and thus cannot be modified. The solution is to get a fresh copy of those records and try again. In my case, it meant get a fresh copy of the parent before saving record B.