# `PhoenixKit.Users.Permissions.AdminGrants`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.64.1/lib/phoenix_kit/users/permissions/admin_grants.ex#L1)

Boot-time grant of permission keys to the Admin role, with retry and a way to
wait for it.

Started by `PhoenixKit.Supervisor`. On start it runs
`PhoenixKit.Users.Permissions.auto_grant_new_keys_to_admin/0` and
`backfill_media_sub_permissions/0`, then waits for `PhoenixKit.Dashboard.Registry`
to finish loading its tabs (which register and grant their own keys).

A key that hits a database error is not dropped until the next restart: it
is kept as pending and tried again after a backoff (1 second, doubled on each
attempt, 6 attempts by default: about 63 seconds in all). The retry is a server-side timer rather than
a retry on the next access because nothing reads these grants lazily — a
request that needs the key would simply be denied — and because a transient
failure (pool exhaustion, a database restart) usually clears within seconds.
Each grant is idempotent and sets its `auto_granted_perm:<key>` flag only on
success, so a retry never overrides an Owner's later revocation. A failure
reported later from outside the sweep (`Permissions.auto_grant_to_admin_roles/1`
called by the tab registry or by `register_custom_key/2`) joins the same retry.

Only a database error (an exception or an exit) is retried. A rejected
changeset, or a missing sandbox owner (a test suite that already switched to
`:manual`), would fail again, so it is logged once and counted as failed.
Each key logs at error level on its first pass, at warning on a retry, and
giving up logs one error with the keys still missing.

A key given up on stays reported (by `await/2`) until the next restart: the
server cannot see a grant made behind its back.

The server is `:permanent`: every step that touches the database rescues, so
it does not crash-loop and take `PhoenixKit.Supervisor` with it.

`PhoenixKit.Users.Permissions.await_admin_grants/1` waits for the result; the
retries take about 63 seconds, so a shorter timeout can return `:timeout`
while they are still running.

# `await`

```elixir
@spec await(timeout(), GenServer.server()) ::
  :ok | {:error, :timeout | {:failed, list()} | {:exit, term()}}
```

See `PhoenixKit.Users.Permissions.await_admin_grants/1`.

# `report_failure`

```elixir
@spec report_failure(String.t() | :media_subs, GenServer.server()) :: :ok
```

Queues a key (or `:media_subs`) whose grant just failed for a retry. A no-op
when the server is not running.

# `start_link`

Options: `:name`, `:retry_base_ms` (first backoff, default 1000),
`:max_attempts` (default 6) and `:sync_registry` (default `true`),
`:sweep_fun` and `:registry_fun` (test seams for the sweep and the registry wait).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
