Summary
The storageMappings GraphQL query fails an entire request when the caller holds a bundle-only grant — one whose legacy capability is None and whose capability comes solely from bundles.
Cause
The resolver authorizes and displays capability through two functions that disagree about bundles:
authorized_prefixes selects prefixes with bits.is_superset(min_bits), where the bits come from effective_bits(legacy, bundles). A bundle-only grant qualifies, so its rows are fetched.
- Per row, the resolver calls
tables::UserGrant::get_user_capability, which maps each node to its .legacy capability, drops None, and takes the max. For a bundle-only grant it returns None, and the resolver's .ok_or_else(...) at storage_mappings.rs converts that into a hard error: missing capability for catalog prefix '…'.
A row readable only through a bundle-only grant therefore aborts the whole query instead of being returned.
Blast radius
Deprecating the by filter (#3200) makes this much easier to trigger. In unfiltered mode, a single bundle-only grant anywhere in the caller's grant graph pulls matching rows into the result set and fails their entire listing — not just the affected row.
Scope
The failing combination (authorized_prefixes + a get_user_capability that hard-errors on None) is currently unique to storageMappings. The root-cause function get_user_capability — legacy-only, None-dropping — is also consumed at server/mod.rs:153; that path should be reviewed for the same divergence. A proper fix reports bundle-aware capability rather than the legacy enum, so it likely spans more than this one resolver.
Fix options
- Expose
capabilityBits (bundle-aware) and stop treating an absent legacy capability as an error.
- Or, minimally: since
authorized_prefixes already established read access to the row's prefix, degrade gracefully instead of failing the request when the legacy capability is None.
Gate
Only reachable once bundle-only user grants can actually exist. If they cannot yet, this is a latent bug to fix before that lands.
Filed from #3200 review (@jshearer).
Summary
The
storageMappingsGraphQL query fails an entire request when the caller holds a bundle-only grant — one whose legacycapabilityisNoneand whose capability comes solely frombundles.Cause
The resolver authorizes and displays capability through two functions that disagree about bundles:
authorized_prefixesselects prefixes withbits.is_superset(min_bits), where the bits come fromeffective_bits(legacy, bundles). A bundle-only grant qualifies, so its rows are fetched.tables::UserGrant::get_user_capability, which maps each node to its.legacycapability, dropsNone, and takes the max. For a bundle-only grant it returnsNone, and the resolver's.ok_or_else(...)at storage_mappings.rs converts that into a hard error:missing capability for catalog prefix '…'.A row readable only through a bundle-only grant therefore aborts the whole query instead of being returned.
Blast radius
Deprecating the
byfilter (#3200) makes this much easier to trigger. In unfiltered mode, a single bundle-only grant anywhere in the caller's grant graph pulls matching rows into the result set and fails their entire listing — not just the affected row.Scope
The failing combination (
authorized_prefixes+ aget_user_capabilitythat hard-errors onNone) is currently unique tostorageMappings. The root-cause functionget_user_capability— legacy-only,None-dropping — is also consumed at server/mod.rs:153; that path should be reviewed for the same divergence. A proper fix reports bundle-aware capability rather than the legacy enum, so it likely spans more than this one resolver.Fix options
capabilityBits(bundle-aware) and stop treating an absent legacy capability as an error.authorized_prefixesalready established read access to the row's prefix, degrade gracefully instead of failing the request when the legacy capability isNone.Gate
Only reachable once bundle-only user grants can actually exist. If they cannot yet, this is a latent bug to fix before that lands.
Filed from #3200 review (@jshearer).