# `Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates`
[🔗](https://github.com/ryanzidago/bylaw/blob/v0.1.0-alpha.1/lib/bylaw/ecto/query/checks/explicit_visibility_predicates.ex#L1)

Validates that configured visibility-sensitive fields are explicitly constrained.

This check is about query explicitness, not visibility correctness. Callers
configure the schema fields that affect record visibility or lifecycle in
their application, such as `:deleted_at`, `:archived_at`, `:hidden_at`,
`:status`, `:state`, or `:published_at`. Bylaw only verifies that queries
against configured schemas mention those fields in supported root `where`
predicates.

## Examples

Bad:

    from(Post, as: :post)
    |> where([post: p], p.organization_id == ^organization_id)

Why this is bad:

If `Post` is configured with `fields: [:deleted_at]`, this query does not say
whether soft-deleted rows should be visible. The visibility decision is left
implicit.

Better:

    from(Post, as: :post)
    |> where([post: p], p.organization_id == ^organization_id)
    |> where([post: p], is_nil(p.deleted_at))

Why this is better:

The root predicate states the visibility decision directly: only rows without
`deleted_at` are requested.

Better when archived rows are intentional:

    from(Post, as: :post)
    |> where([post: p], p.archived_at <= ^cutoff)

## Notes

This check verifies explicitness, not visibility correctness. It accepts
supported root predicates that mention configured fields, but it cannot prove
predicates hidden inside fragments or subqueries.

The check is static. It accepts configured root fields when they appear
directly in `where` expressions, including `is_nil(field)`, `not is_nil(field)`,
bare field predicates, comparisons against values or parameters, and `in`
predicates whose right side has no field references. Field-to-field
comparisons are not treated as explicit constraints. It cannot prove visibility
fields hidden inside raw SQL fragments or subqueries. Combination queries such
as `union`, `union_all`, `except`, and `intersect` validate the parent query
and every combination branch independently.

When the root query schema is not configured, the check returns `:ok`.
Configured fields that do not exist on the root schema are ignored. If no
applicable configured fields remain, the check returns `:ok`.

## Options

  * `:validate` - explicit `false` disables the check. Defaults to `true`.
  * `:schemas` - list of `{schema, fields: fields}` tuples. Defaults to `[]`.

Example check spec:

    {Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates,
     schemas: [{Post, fields: [:deleted_at, :archived_at]}]}

## Usage

Add this module to the explicit check list passed through `Bylaw.Ecto.Query`.
See `Bylaw.Ecto.Query` for the full `c:Ecto.Repo.prepare_query/3` setup.

# `validate`

```elixir
@spec validate(
  Bylaw.Ecto.Query.Check.operation(),
  Bylaw.Ecto.Query.Check.query(),
  opts()
) ::
  Bylaw.Ecto.Query.Check.result()
```

Implements the `Bylaw.Ecto.Query.Check` validation callback.

---

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