# `Lightning.Policies.Decision`
[🔗](https://github.com/OpenFn/lightning/blob/main/lib/lightning/policies/decision.ex#L1)

The order every `%Scope{}` policy decides in, so each policy supplies only its
token grant and its rule. `project/4` decides actions on a project and
`instance/4` actions with no project.

The order keeps refusals from leaking. A caller without the grant hears
`{:error, {:insufficient_scope, grant}}` before anything about the target. A
caller the rule refuses who has no standing on the project hears
`{:error, :no_standing}`, before the MFA check, so it learns nothing about the
project, not even that it exists. Only a caller every other check allows
learns `{:error, :scheduled_for_deletion}`.

`allowed?` is computed before the call, so it reads only facts already on the
scope and subject. A rule that needs another project's facts loads that
project's scope through `Lightning.Scope`, outside `allowed?` and only after
`Decision` has answered for the subject's own project. `:see_credential` does
this for a credential's linked projects, and the sandbox rules for a
sandbox's root project.

A `:before_target` subject asks only about the grant, so `true` for it is not
permission. Only `Lightning.Policies.Permissions.permit/3` passes it, so that
`LightningWeb.Plugs.AuthorizeAction` can refuse a missing grant before
anything is loaded.

# `answer`

```elixir
@type answer() ::
  boolean()
  | {:error, insufficient_scope() | :no_standing | :scheduled_for_deletion}
```

# `grants`

```elixir
@type grants() :: String.t() | [String.t(), ...]
```

# `insufficient_scope`

```elixir
@type insufficient_scope() :: {:insufficient_scope, grant :: String.t()}
```

The refusal of a caller whose token lacks `grant`, the one grant the
refusing check needed.

# `instance`

```elixir
@spec instance(Lightning.Scope.t(), term(), grants(), boolean()) :: answer()
```

Decides an action with no project, answering `allowed?` once one of `grants`
is held.

# `project`

```elixir
@spec project(Lightning.Scope.t(), term(), grants(), boolean()) :: answer()
```

Decides an action on a project. `grants` is one token grant or a list, any
one of which is enough; holding none, the answer is
`{:error, {:insufficient_scope, grant}}` naming the first. A refused caller
with no standing on the project hears `{:error, :no_standing}`, and
`{:error, :scheduled_for_deletion}` comes only to a caller every other check
allows.

---

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