datumctl assistant gaps list returns 403 in staging, so a provider cannot see the capability gaps filed against their service — the whole point of the aggregate view.
The permission is granted correctly on paper: Role assistant.miloapis.com-conversation-viewer is Ready=True with capabilitygaps.list in its status.effectivePermissions, and the ProtectedResource is Ready=True. The authorizer denies it anyway, and SelfSubjectAccessReview reports an explicit denied: true.
Cause is an ordering race at rollout. Flux applied the Role and the ProtectedResource together, but the Role's tuples were written into OpenFGA at 22:46:46Z while the capabilitygaps relation only appeared at 22:48:59Z — so the grant was recorded against a model with no such relation. The Role has not changed generation since, so nothing re-triggers a reconcile and it does not self-heal. capabilitygapreports is unaffected because its ProtectedResource predates it by days.
Two things to decide: how to unblock staging now (a Role reconcile re-trigger clears it), and whether the projection should retry when a permission names a resource whose relation does not exist yet. Any future resource added alongside its Role will hit the same race.
datumctl assistant gaps listreturns 403 in staging, so a provider cannot see the capability gaps filed against their service — the whole point of the aggregate view.The permission is granted correctly on paper:
Role assistant.miloapis.com-conversation-viewerisReady=Truewithcapabilitygaps.listin itsstatus.effectivePermissions, and theProtectedResourceisReady=True. The authorizer denies it anyway, andSelfSubjectAccessReviewreports an explicitdenied: true.Cause is an ordering race at rollout. Flux applied the Role and the ProtectedResource together, but the Role's tuples were written into OpenFGA at 22:46:46Z while the
capabilitygapsrelation only appeared at 22:48:59Z — so the grant was recorded against a model with no such relation. The Role has not changed generation since, so nothing re-triggers a reconcile and it does not self-heal.capabilitygapreportsis unaffected because its ProtectedResource predates it by days.Two things to decide: how to unblock staging now (a Role reconcile re-trigger clears it), and whether the projection should retry when a permission names a resource whose relation does not exist yet. Any future resource added alongside its Role will hit the same race.