CatalogPath
A location shape inside a document: the same list of steps as a app.oreshkov.rabosh.variant.VariantPath, except that array indices are collapsed into a single CatalogStep.AnyElement.
{"items":[{"sku":"a"},{"sku":"b"}]}
$ object
$.items array
$.items[*] object, twice
$.items[*].sku string, twiceWhy this is not VariantPath. That type's contract is that it names exactly one location, which is what makes it usable as an index key and what lets Variant.select return one value. Adding a wildcard to it would break that guarantee for every existing caller. So the catalog gets its own type, and the relationship is one-way: every VariantPath has a CatalogPath shape, and a CatalogPath describes a set of VariantPaths.
That set is not abstract. forEachNodeIn enumerates it against a document — which is the only way it can be enumerated, since [*] stands for as many locations as that document has elements. The direction stays one-way: there is no CatalogPath.toVariantPath, and there cannot be.
Why the indices are collapsed at all. A ten-thousand-element array would otherwise produce ten thousand paths, exhaust the path budget on its own, and push everything genuinely worth modelling into the overflow bucket. Collapsing also produces the path an inverted index over array elements actually wants — $.tags[*] is the thing somebody queries, $.tags[7] is not. ClickHouse's JSON type and the Variant shredding specification both make the same choice.
Paths are ordered so that a report is stable between runs: field before element at the same position, then by field name, then shorter first.