# `AshGrant.Check`
[🔗](https://github.com/jhlee111/ash_grant/blob/v0.14.1/lib/ash_grant/checks/check.ex#L1)

SimpleCheck for write and generic actions.

This check integrates with Ash's policy system to provide permission-based
authorization for write operations and generic actions. It returns `true` or
`false` based on whether the actor has the required permission.

For read actions, use `AshGrant.FilterCheck` instead, which returns a filter
expression to limit query results.

> #### Auto-generated Policies {: .info}
>
> When using `default_policies: true` in your resource's `ash_grant` block,
> this check is automatically configured for write actions and generic actions.

## When to Use

Use `AshGrant.check/1` for:
- `:create` actions
- `:update` actions
- `:destroy` actions
- `:action` (generic actions)

## Usage in Policies

    policies do
      # For all write actions
      policy action_type([:create, :update, :destroy]) do
        authorize_if AshGrant.check()
      end

      # For generic actions (not included in default_policies)
      policy action_type(:action) do
        authorize_if AshGrant.check()
      end

      # For a specific action
      policy action(:publish) do
        authorize_if AshGrant.check(action: "publish")
      end
    end

## Generic Actions

Generic actions (Ash actions with `type: :action`) use `Ash.ActionInput`
instead of `Ash.Query` or `Ash.Changeset`. This check correctly extracts
tenant from `action_input` for multi-tenant authorization.

Generic actions must be authorized by their specific action name in the
permission string — type wildcards (`action*`) do not apply because generic
actions are individually unique:

    # Permission grants access to the specific "ping" action
    "service_request:*:ping:always"

    # Wildcard (*) grants access to all actions including generic ones
    "service_request:*:*:always"

Since generic actions have no target record, only `scope :always, true` (or
other non-record scopes) will pass scope evaluation. Record-based scopes
like `scope :own, expr(author_id == ^actor(:id))` are not applicable.

> #### default_policies and generic actions {: .info}
>
> `default_policies: true` automatically generates a policy for generic actions
> using `AshGrant.Check`.

## Options

| Option | Type | Description |
|--------|------|-------------|
| `:action` | string | Override action name for permission matching |
| `:resource` | string | Override resource name for permission matching |

## How It Works

1. **Resolve permissions**: Calls the configured `PermissionResolver` to get
   the actor's permissions
2. **Check access**: Uses `AshGrant.Evaluator.has_access?/3` to verify
   the actor has a matching permission (deny-wins semantics)
3. **Get scope**: Extracts the scope from the matching permission
4. **Verify scope**: Uses `Ash.Expr.eval/2` to evaluate the scope filter
   against the target record

## Scope Evaluation

Scope filters use `Ash.Expr.eval/2` for proper Ash expression handling:
- Full support for all Ash expression operators
- Automatic actor template resolution (`^actor(:id)`, etc.)
- Automatic tenant template resolution (`^tenant()`)
- Context injection via `^context(:key)` for testable scopes
- Handles nested actor paths

For **update/destroy** actions:
- The scope filter is evaluated against the existing record (`changeset.data`)

For **create** actions:
- A "virtual record" is built from the changeset attributes
- The scope filter is evaluated against this virtual record

## Dual Read/Write Scope

This check uses `AshGrant.Info.resolve_write_scope_filter/3` for scope resolution,
which checks the scope's `write:` option first, falling back to `filter` if not set.
This allows scopes to provide separate expressions for reads and writes.

The `write:` option is an explicit override. When omitted, the check automatically
chooses the best strategy for the scope expression (see "DB Query Fallback" below).

    # Explicit in-memory override (avoids DB round-trip)
    scope :same_org, expr(exists(org.users, id == ^actor(:id))),
      write: expr(org_id == ^actor(:org_id))

Set `write: false` to explicitly deny writes with a scope:

    scope :readonly, expr(exists(org.users, id == ^actor(:id))),
      write: false

## Relational Scopes and DB Query Fallback

Scopes using `exists()` or dot-path references cannot be evaluated in-memory.
When such a scope has no explicit `write:` option and the resource has a data layer,
the check automatically uses a **DB query** to verify the scope instead:

| `write:` value | Strategy | Behavior |
|---|---|---|
| `write: false` | Deny | Returns false immediately |
| `write: true` | Allow | Returns true immediately |
| `write: expr(...)` | In-memory | Evaluate custom expression |
| _(omitted, no relationships)_ | In-memory | Current behavior |
| _(omitted, has relationships)_ | **DB query** | Query DB with read scope |

**For update/destroy**: Queries the DB to check if the existing record matches
the read scope expression.

**For create**: Splits the filter into direct-attribute parts (evaluated in-memory)
and relationship parts. Relationship parts are verified by extracting the FK from
the changeset and querying the parent resource.

This means scopes like `exists(team.memberships, user_id == ^actor(:id))` now work
correctly for all action types without requiring a `write:` option.

## Examples

### Basic Usage

    # Permission: "post:*:update:own"
    # Actor can only update their own posts

    policy action(:update) do
      authorize_if AshGrant.check()
    end

### Action Override

    # The Ash action is :publish, but we check for "update" permission
    policy action(:publish) do
      authorize_if AshGrant.check(action: "update")
    end

## See Also

- `AshGrant.FilterCheck` - For read actions
- `AshGrant.Evaluator` - Permission evaluation logic
- `AshGrant.Info` - DSL introspection helpers

# `check`

Creates a check tuple for use in policies.

## Examples

    policy always() do
      authorize_if AshGrant.check()
    end

    policy action(:destroy) do
      authorize_if AshGrant.check(subject: [:status])
    end

# `conflicts?`

Determines if two check references conflict (are mutually exclusive).

AshGrant checks don't inherently conflict with each other. The deny-wins
semantics are handled at permission evaluation time, not at the check level.

Returns `false` for all cases.

# `eager_evaluate?`

# `implies?`

Determines if one check reference implies another.

Two AshGrant checks imply each other if they have identical options (same action
and resource overrides). This helps the SAT solver avoid redundant evaluations.

## Examples

    # Same check implies itself
    implies?({Check, []}, {Check, []}, context) == true

    # Different actions don't imply each other
    implies?({Check, [action: "read"]}, {Check, [action: "update"]}, context) == false

# `init`

# `prefer_expanded_description?`

# `requires_original_data?`

# `simplify`

Simplifies a check reference into a SAT expression of simpler check references.

For AshGrant checks, we return the ref unchanged since permissions are resolved
dynamically at runtime and cannot be further decomposed statically.

This callback is used by Ash's SAT solver to optimize policy evaluation.

# `strict_check`

# `type`

---

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