Thankfully, what you're asking for isn't really all that tough to do.
First you need to override a GPNodeBuilder (or choose one wisely) to
guarantee that resetNode() is called on the parent nodes before it's
called on their children, and that the parent is set before
resetNode() is called. Most node builders do the former but not the
latter. Here's what I would do: create a subclass of your preferred
GPNodeBuilder which builds the tree as normal, then goes through it
depth-first and calls resetNode AGAIN. In resetNode you just check to
see if your parent is null, and if it is you immediately return
(you'll get called again, don't worry), else you reset the node,
constrained by the parent value.
This can be a challenging problem because of crossover or similar bulk-
tree-modification operators. If you cross over a tree it may now be
the case that the child at the crossover point is no longer allowed to
be a child of the new parent because of your constraint. So you'll
need to create a new CrossoverPipeline subclass which overrides the
verifyPoints method to determine if crossover is legal at that position.
Don't fool with GPNodeConstraints or typing: they're more aimed at
constraints that are finite in number and you have an infinite number
of constraint mappings.