The signature of sum/3 is sum(+Vars, +Rel, ?Expr), which informally should be read as Vars must be instantiated to a list. As the first iteration of append/3 binds the 3th argument to a partial list, freeze/2 triggers sum/3 too early.
Constraints relax ordering problems, but does not fully remove them … Of course, this could be fixed inside sum/3, after which you no longer need freeze/2.
The third argument becomes a proper list eventually. But freeze/2 fires as soon as its first argument is not a free variable. At this point the list looks like [1|_], which is indeed a partial list. This should be visible in the full error message you get.
This is a special-purpose predicate, due to the deliberately-delayed unification, which is against the fail-fast principle, because Both can only be compared at the end.
Using when(ground(Ns), …) solves this example problem. However in my actual project ground(Ns) is too strong and I do need freeze to allow Ns to be a list of FD variables constrained by sum/3.
Yes, this solution is similar to my workaround suggested in the initial question. It does work, but just feeling append/3 should address it internally.
This is a specialized use-case. append/3 should not be weakened in its fail-fast property, just for this use-case. Can use custom code instead of append/3