ancestors Of
Walks the parent chain upward, one keyed lookup per hop, at most limit hops.
Why the walk is here and not in Cypher. A parent is a property, not a relationship: a run can name a parent that has not been stored yet, and an edge cannot point at a node that does not exist. Materializing the edge later would mean a second write nothing triggers. Without an edge there is no variable-length pattern to walk, and the APOC procedures that would do it in one round trip are not a dependency this module takes.
So the walk is client-side and bounded by construction: at most limit hops, each a lookup on the uniqueness-constraint index, all inside one read transaction. It stops on a run it has already seen, which is what makes it safe on a corrupt store holding a cycle — ExtractionRunLineage can reject a run that is its own parent, but a two-hop cycle needs the other runs to see. And it resolves every hop inside the starting run's tenant, so a parent id that exists only in a neighbour's tenant resolves to nothing and the walk ends.