This is related to #14150 and possibly others, but it's actually a separate issue.

For instance, when using the following query:
`bitcoin-cli scantxoutset start '[{"desc":"wpkh(xpubABCDEF/0/*)", "range":100}]'`
I can get a list of UTXOs, but no way to get back the index which derived that particular UTXO. So it's not much better than deriving on the user side and specifying addresses one by one, so that I already have a cache of addresses to match.

Not sure what the solution looks like. One way is to repeat the full descriptor in the utxo. That might get verbose though.
virtual std::string ToString() const = 0;
    
    virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
    /** Expand a descriptor at a specified position.
     *
     * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
     * provider: the provider to query for private keys in case of hardened derivation.
     * output_script: the expanded scriptPubKeys will be put here.
     * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
     */
    virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
};
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out);
/** Find a descriptor for the specified script, using information from provider where possible.
 *
 * A non-ranged descriptor which only generates the specified script will be returned in all
 * circumstances.
 *
 * For public keys with key origin information, this information will be preserved in the returned
 * descriptor.
 *
 * - If all information for solving `script` is present in `provider`, a descriptor will be returned
 *   which is `IsSolvable()` and encapsulates said information.
 * - Failing that, if `script` corresponds to a known address type, an "addr()" descriptor will be
 *   returned (which is not `IsSolvable()`).
 * - Failing that, a "raw()" descriptor is returned.
 */
std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider);
#endif