All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.14.1] - 2026-04-13
Two fixes that make resolve_argument (introduced in 0.14.0) actually
usable outside AshGrant.PolicyTest — both bugs rendered the DSL sugar
a silent no-op in production. Also bumps the hard Ash floor to 3.19 for
compatibility-CI parity.
Fixed
resolve_argumentwas a silent no-op for non-plain-map actors (#101). Theneeds_resolution?/3optimization read permissions straight offactor.permissionsand returned[]for any actor that was not a literal map with a:permissionskey. Real Ash resource structs carry no such field — permissions come from the configuredPermissionResolver— so the change never ran in production, the argument stayednil, and argument-based scopes always denied.AshGrant.Changes.ResolveArgumentnow routes through the resource's configured resolver (same source asAshGrant.Check/FilterCheck) and conservatively resolves the argument when the resolver is absent or raises, rather than skipping.resolve_argumentsilently failed on CREATE for attribute-multitenant targets (#99).AshGrant.Changes.ResolveArgumentdid not forward the changeset's tenant toAsh.get!/Ash.load!, so whenever any hop infrom_pathpointed to a resource withmultitenancy strategy: :attribute, the fetch raised, the rescue returnednil, and the argument-based scope evaluated tofalse— denying the action. The change now passestenant: changeset.tenantto both the create-pathsafe_get/3and the update/destroy-pathsafe_load/3.
Changed
- Ash floor bumped from
~> 3.7to~> 3.19(#98). Aligns the declared minimum with the version the compatibility CI matrix already exercises.
Documentation
- ExDoc now surfaces the
scope-naming-convention.mdandargument-based-scope.mdguides in its extras list (previously authored but unlinked inmix.exs). AshGrant.ArgumentAnalyzer@moduledocno longer references a removed helper onAshGrant.Check.
[0.14.0] - 2026-04-13
This release is centred on a new argument-based scope pattern for
multi-hop authorization, fixes a related security bypass in composite
scope routing, and begins deprecating the write: scope option in favour
of the new pattern.
Added
resolve_argumentDSL entity for argument-based scopes (#90). Declare a scope that compares an action argument (^arg(:name)) to actor attributes, and let the resource populate the argument from its own relationships:ash_grant do scope :at_own_unit, expr(^arg(:center_id) in ^actor(:own_org_unit_ids)) resolve_argument :center_id, from_path: [:order, :center_id] endThe transformer validates the path, detects dead declarations, and auto-injects an argument + a lazy change on every write action. The change only performs the DB load when an in-play permission uses a scope that actually references the argument — direct-attribute scopes pay zero cost. Multi-hop paths (e.g.,
[:order, :customer, :organization_id]) are supported. See the new Argument-Based Scope guide.^arg(:name)templates in scope expressions now resolve correctly at strict-check time (#88). PreviouslyAshGrant.Checknever forwardedchangeset.argumentstoAsh.Expr.fill_template, so any scope using^arg(...)crashed withBadMapErrorbefore this release.Explain surface for
resolve_argument(#93).AshGrant.Explanationgains a:resolve_argumentsfield populated byExplainer.explain/4, andExplanation.to_string/2renders a new "Argument Resolution" section showing each declared resolver, its path, and which scopes trigger it.PolicyTest support for action arguments (#93).
assert_can/assert_cannotaccept a keyword-list third argument —[record: ..., arguments: ...]— andYamlParserparses anarguments:field on each test.DslGeneratoremits the keyword-list form when converting YAML→DSL.
Fixed
- Composite scope on create security bypass (#87, closes #83).
should_use_db_query?/3now inspects the resolved filter (with inheritance applied) instead of the child scope's rawscope_def.filter. Composite scopes inheriting a relational parent (exists()or dot-path) no longer skip the DB query fallback on create actions. Before this fix,:at_own_unit_and_small(inheriting a relational:at_own_unitparent) could silently allow unauthorized creates when the parent'sexists()condition evaluated truthy against a virtual record. - Detector coverage for function-wrapped relational references and lists (#87).
contains_relationship_reference?/1now descends into%{__function__?: true, arguments: ...}structs, list RHS ofinoperators, and handlesnilexplicitly.
Deprecated
write:option onscope(#91). The option was introduced as an escape hatch for relational scopes that couldn't be evaluated in memory on writes. Withresolve_argumentnow providing a first-class way to express multi-hop authorization via in-memory-evaluable scopes,write:is redundant for the common case. Using it still works but emits a compile-time deprecation warning pointing at the replacement pattern. A regression test (#92) asserts the warning emits with the correct message.
Changed
- Scope naming: the unrestricted scope is renamed from
:allto:alwaysacross the codebase, guides, and test fixtures (#84)."all"is still accepted as a boolean-true scope for backward compatibility; new code should use:always. A Scope Naming Convention guide documents the "sentence test" that motivated the rename.
Documentation
- New Argument-Based Scope guide with full Refund → Order → center_id example, hand-rolled "under the hood" section, safety analysis, and gotchas (#89).
- Cross-links from
authorization-patterns.md,checks-and-policies.md,policy-testing.md,scope-naming-convention.md,debugging-and-introspection.md,scopes.md,getting-started.md(#94). usage-rules.md(the AI-agent-facing rulebook) rewritten to recommendresolve_argument, flagwrite:as deprecated, and document the new DSL entity (#95)./prand/releaseskill doc gates extended to checkusage-rules.mdandguides/*.md— structural fix so these files aren't silently missed in future PRs (#95).- New tip in
guides/scopes.mdpreferring direct FK column (is_nil(team_id)) over relationship traversal (is_nil(team.id)) when the check is really about the FK itself (#96).
Follow-up / related
- #86 (DB-query fallback limitations on function-wrapped relational refs during create) was closed as "not planned" — the argument-based pattern covers the motivating cases cleanly, and the fallback path is no longer the recommended route for authorization involving relationships.
[0.13.5] - 2026-04-08
Changed
- Remove noisy
:all/:alwaysscope warning: The compile-time warning fromValidateScopesthat fired for every resource without a universal scope has been removed. This was a best-practice hint rather than a real validation, and produced excessive noise in projects with many resources — especially with--warnings-as-errors. (#81)
[0.13.4] - 2026-04-06
Fixed
- Generic action authorization:
AshGrant.Checknow correctly extracts tenant fromaction_inputfor generic actions (type:action). Previously,get_tenant/1only handledqueryandchangeset, causing tenant-aware resolvers to return empty permissions for generic actions. The same fix is applied toFilterCheckandFieldCheck. (#76) default_policiesnow covers generic actions:AddDefaultPoliciestransformer generates a policy foraction_type(:action)usingAshGrant.Check. Previously, resources withdefault_policies trueand generic actions had no matching policy, resulting in forbidden. (#76)- Write scope evaluation: Use
fill_templateand passresource:option toAsh.Expr.evalfor correct template resolution and attribute hydration in write scope checks. (#75)
[0.13.2] - 2026-03-29
Changed
- Action type wildcard (
read*) no longer matches by string prefix. Previously,read*matched both actions starting with "read" (e.g.,read_all) and actions with:readaction type (e.g.,list). Nowread*matches only by action type — useread(exact) for the action named "read". This prevents false matches where an action namedread_somethingcould be a non-read type. (#72)
[0.13.1] - 2026-03-29
Added
- Authorization Patterns guide: New ExDoc guide covering RBAC, ABAC, ReBAC, and additional patterns (deny-wins, multi-tenancy, field-level access, domain inheritance, CanPerform). Includes practical scope DSL examples for each pattern and a comparison table. (#70)
- Usage rules: Document
instance_keyandscope_throughin policy test fixtures. (#69)
[0.13.0] - 2026-03-24
Added
instance_keyDSL option: Match instance permission IDs against a field other than:id. For example,instance_key :feed_idmakes"feed:feed_abc:read:"generateWHERE feed_id IN ('feed_abc')instead ofWHERE id IN ('feed_abc'). Works with FilterCheck, Check, and CanPerform. (#62)scope_throughentity: Propagate parent resource instance permissions to child resources viabelongs_torelationships. When a user has"feed:feed_abc:read:", addingscope_through :feedto the child resource grants access to all child records wherefeed_id == "feed_abc". Supports action filtering withactions:option. (#62)ValidateScopeThroughstransformer: Compile-time validation thatscope_throughreferences validbelongs_torelationships.
Changed
- Documentation refactored into ExDoc guides: README trimmed from 1,687 to 166 lines. Content split into 7 focused guides (Getting Started, Permissions, Scopes, Field-Level Permissions, Checks & Policies, Debugging & Introspection, Policy Testing) with ExDoc sidebar grouping under "Guides". (#65)
- Issue #65 documentation improvements: Action wildcard type clarification (
read*matches action type, not string prefix), instance permission boundary note, per-actiondefault_policiessubsection, RBAC + instance OR combination example, relational scopes tip in Getting Started.
[0.12.0] - 2026-03-16
Added
CanPerformcalculation for per-record UI visibility: NewAshGrant.Calculation.CanPerformmodule produces per-record boolean values (e.g.,:can_update?,:can_destroy?) that compile to SQL viaexpression/2— no N+1 queries. Supports RBAC scopes, instance permissions, deny-wins, and multi-scope OR combination. (#58)can_performDSL entity: Declare individual CanPerform calculations inline in theash_grantblock with optional custom naming (e.g.,can_perform :read, name: :visible?). The transformer auto-detects the resource module.can_perform_actionsbatch option: Generate multiple CanPerform calculations at once (e.g.,can_perform_actions [:update, :destroy]generates:can_update?and:can_destroy?).- Compile-time action name validation:
can_performandcan_perform_actionsnow raiseSpark.Error.DslErrorat compile time if a referenced action does not exist on the resource, preventing typos from silently producing always-false calculations. (#60) AshGrant.Info.can_perform_actions/1: Introspection helper to query configured batch actions.
[0.11.1] - 2026-03-15
Changed
- Documentation: Add domain-level DSL section to README with usage guidance, inheritance rules, and examples. Update installation version, feature list, and DSL Configuration table.
- Developer tooling: Add
/prand/releaseslash commands for streamlined PR and release workflows with built-in documentation gates. Update CLAUDE.md architecture section.
[0.11.0] - 2026-03-15
Added
- Domain-level DSL (
AshGrant.Domain): Define sharedresolverandscopeat the Ash Domain level. Resources using theAshGrantextension automatically inherit domain config, eliminating repeatedash_grant doblocks across resources. Resource-level settings take precedence (resolver override, same-name scope override). Cross-boundary scope inheritance is supported (resource scope can inherit from a domain-defined parent). (#54)
[0.10.3] - 2026-03-15
Fixed
default_field_policiesincludes PK/timestamps, fails on OTP 28: Whenfield_group :admin, :allis used withdefault_field_policies true, generated field policies now exclude primary keys and non-public attributes (e.g.created_at,updated_at). Previously these invalid fields causedSpark.Error.DslErroron OTP 28 due to transformer ordering differences. (#51)
Changed
- Resolve all credo issues: Fixed 82 credo warnings including
length/1comparisons,Enum.map_joinusage, negated conditions, nesting depth, and cyclomatic complexity.mix credonow reports zero issues.
0.10.2 - 2026-03-13
Fixed
- Overlapping field_policies with
:allfield_groups: When multiplefield_groupdefinitions use:all(or:all, except:), resolved fields overlapped across policies. Since Ash requires ALL matchingfield_policyentries to pass, fields in multiple groups were denied even when the actor had the correct permission.AddFieldPoliciesnow deduplicates fields across groups so each field appears in exactly one policy. (#48)
0.10.1 - 2026-03-12
Fixed
- Action prefix patterns now match by Ash action type:
read*matches any:read-type action (e.g.,list_published,by_slug,search), not just actions whose name starts with"read". Same forcreate*,update*,destroy*. (#46) - Explainer prefix matching:
Explainer.matches_action?/2only did exact match — now usesPermission.matches_action?/3with full prefix and action-type support.
0.10.0 - 2026-03-12
Changed (BREAKING)
field_groupDSL redesign (#40): Removed positional argument ambiguity betweeninheritsandfields- BREAKING:
inheritsis now keyword-only:field_group :name, [:parents], [:fields]no longer works. Usefield_group :name, [:fields], inherits: [:parents]instead. - BREAKING:
[:*]replaced by:all: Usefield_group :name, :allinstead offield_group :name, [:*](deprecated[:*]still works with a warning, will be removed in v1.0.0) - New
:allsyntax:field_group :admin, :all— all resource attributes - Blacklist mode:
field_group :public, :all, except: [:salary, :ssn] - Combined:
field_group :editor, :all, except: [:admin_notes], inherits: [:base] - Most common pattern unchanged:
field_group :public, [:name, :department]works as before
- BREAKING:
Migration Guide
# Before (v0.9.0)
field_group :public, [], [:*], except: [:salary, :ssn]
field_group :sensitive, [:public], [:phone, :address]
field_group :confidential, [:sensitive], [:salary, :email]
# After (v0.10.0)
field_group :public, :all, except: [:salary, :ssn]
field_group :sensitive, [:phone, :address], inherits: [:public]
field_group :confidential, [:salary, :email], inherits: [:sensitive]Deprecated
[:*]wildcard syntax: Use:allinstead.[:*]still works but emits a compile-time deprecation warning. Will be removed in v1.0.0.
0.9.0 - 2026-03-12
Added
field_groupexceptoption (blacklist mode): Use[:*]wildcard withexceptto exclude specific fields instead of listing all visible ones. Useful for resources with many attributes where only a few are sensitive. (#36)field_group :public, [], [:*], except: [:salary, :ssn]— all attributes except salary and ssn[:*]withoutexceptexpands to all resource attributes- Compile-time validations:
exceptrequires[:*], except fields must exist, masked fields cannot be inexcept - New transformer
AshGrant.Transformers.ResolveFieldGroupExceptresolves wildcards before downstream validation
0.8.1 - 2026-03-11
Fixed
- DB query fallback now handles
Ash.Query.Call(dot-path expressions): Expressions likeexpr(order.center_id in ^actor(:ids))are now correctly detected as relationship references and trigger the DB query fallback for write actions. Previously onlyexists()expressions were detected. (#33) - Pass
tenant:toAsh.exists?/2calls: Multitenanted resources no longer fail silently during DB query fallback write checks. (#33)
0.8.0 - 2026-03-11
Added
- DB query fallback for relational write scopes: Scopes using
exists()or dot-path references now work correctly for write actions (create, update, destroy) without requiring awrite:option. When a scope has relationship references and no explicitwrite:override,AshGrant.Checkautomatically queries the database using the read scope expression. (#28)- Update/destroy: Queries DB to check if the existing record matches the read scope
- Create: Splits the filter — direct-attribute conditions are evaluated in-memory, relationship conditions are verified via DB query on parent resources
write:option still works as an explicit override (backward compatible)- Resources without a data layer fall back to in-memory evaluation (existing behavior)
Removed
- Compile-time warning for relationship scopes without
write:: The warning is no longer needed since the DB query fallback handles these cases automatically. (#28)
0.7.0 - 2026-03-11
Added
- Dual read/write scope (
write:option): Scopes can now provide a separate expression for write actions via thewrite:option. This solves the authorization bypass whereexists()and dot-path scopes were silently replaced withtrueduring in-memory evaluation for write actions. (#26)write: expr(...)— direct-field expression for in-memory evaluationwrite: false— explicitly deny writes with this scopewrite: true— allow all writes with this scope (no filtering)- Falls back to
filterwhenwrite:is omitted (backward compatible) - Inheritance support: child scopes inherit parent's
write:expression;write: falsepropagates to children - New
AshGrant.Info.resolve_write_scope_filter/3function for write scope resolution
Changed
- Compile-time warning for relationship scopes: The warning for
exists()/dot-path scopes now fires regardless ofdefault_policiessetting and also detects dot-path references (not justexists()). The warning is suppressed when awrite:option is provided. (#26) AshGrant.Checkuses write scope resolution: Write actions now resolve scopes viaresolve_write_scope_filter/3instead ofresolve_scope_filter/3, using thewrite:expression when available. (#26)
0.6.1 - 2026-03-01
Fixed
- Bulk operations crash with
exists()scopes:Ash.bulk_create/4(and bulk_update/bulk_destroy) crashed withnil.persisted(:relationships_by_name)when the resource had anexists()scope expression. The fix replacesexists()nodes withtruebefore in-memory evaluation. Attribute-based conditions in the same scope are still enforced. (#23)
Added
- Compile-time warning for
exists()scopes: Resources withdefault_policiesincluding write actions now emit a warning when scopes containexists(), informing users that the relational condition is not enforced for writes - Documentation: Added "Relational Scopes" section to README and moduledocs explaining the
exists()limitation for write actions
0.6.0 - 2026-02-19
Added
Field-Level Permissions: Column-level read authorization via field groups
field_groupDSL entity with inheritance (DAG-based) and masking support- 5-part permission format:
resource:instance:action:scope:field_group(backward compatible with 4-part) AshGrant.FieldCheck- SimpleCheck for Ashfield_policiesintegrationAshGrant.field_check/1- Public API for use in manualfield_policies(Mode A)default_field_policies: true- Auto-generatefield_policiesfromfield_groupdefinitions (Mode B)- Field group inheritance: child groups include all parent fields
- Field masking with allow-wins semantics via
maskandmask_withoptions
New Modules:
AshGrant.Dsl.FieldGroup- Struct and DSL entity for field group definitionsAshGrant.FieldCheck- SimpleCheck for field-level authorizationAshGrant.Preparations.ApplyMasking- Runtime masking viaafter_actionhookAshGrant.Transformers.AddFieldPolicies- Auto-generatesfield_policiesfrom field groupsAshGrant.Transformers.AddMaskingPreparation- Auto-registers masking preparationAshGrant.Transformers.ValidateFieldGroups- Compile-time validation (duplicates, cycles, missing parents)
New Evaluator Functions:
get_field_group/3- Get first matching field group from permissionsget_all_field_groups/3- Get all matching field groups (union for field access)
New Info Functions:
field_groups/1- Get all field group definitions for a resourceget_field_group/2- Get a specific field group by nameresolve_field_group/2- Resolve a field group with inheritancedefault_field_policies/1- Get thedefault_field_policiessettingfetch_permissions/3- Shared permission resolution helper
Introspect Updates:
actor_permissions,available_permissions,can?, andallowed_actionsnow includefield_groups/field_groupin their responsesExplainer & PolicyExport: Field group information in
explain/4output, Markdown tables, and Mermaid diagrams
Changed
- Permission format: Extended from 4-part to optional 5-part with
field_group - Transformer ordering:
ValidateFieldGroupsnow explicitly runs beforeAddFieldPoliciesandAddMaskingPreparation
0.5.0 - 2026-01-21
Added
Policy Configuration Testing: DSL-based testing framework for verifying policy configurations without a database
AshGrant.PolicyTest- Main module withusemacro for defining policy testsassert_can/2,3andassert_cannot/2,3assertion macrosresource/1,actor/2,describe/2,test/2DSL macrosAshGrant.PolicyTest.Runner- Test execution with summary statisticsAshGrant.PolicyTest.Result- Result struct with timing information
YAML Policy Tests: Alternative format for non-Elixir developers
AshGrant.PolicyTest.YamlParser- Parse and run YAML test filesAshGrant.PolicyTest.YamlExporter- Export DSL tests to YAMLAshGrant.PolicyTest.DslGenerator- Generate DSL code from YAML
Policy Export: Export policy configurations to documentation formats
AshGrant.PolicyExport.Mermaid- Generate Mermaid flowchart diagramsAshGrant.PolicyExport.Markdown- Generate Markdown documentation
Mix Tasks: CLI tools for policy testing and export
mix ash_grant.verify- Run policy configuration testsmix ash_grant.export- Export policies to YAML/Mermaid/Markdownmix ash_grant.import- Convert YAML to Elixir DSL
Dependencies
- Added
yaml_elixir ~> 2.9as optional dependency for YAML support
0.4.1 - 2026-01-21
Added
- SAT Solver Optimization Callbacks: Implements
Ash.Policy.Checkoptional callbacks for smarter authorization decisionssimplify/2- Returns ref unchanged (permissions are runtime-resolved)implies?/3- Returnstruewhen check refs have identical module and optionsconflicts?/3- Returnsfalse(deny-wins is handled at evaluation time)- Enables the authorizer to reach decisions with fewer variables in conditions
- Suggested by Jonatan Männchen (Ash contributor)
Changed
Version management: Removed hardcoded version numbers from documentation
- README.md now references GitHub without specific tag (always uses latest)
@moduledocinstallation section now links to README- Single source of truth:
mix.exs @version
Deprecation timeline: Extended
owner_fielddeprecation from v0.3.0 to v1.0.0- Affects:
dsl.ex,info.ex,validate_scopes.ex,CHANGELOG.md
- Affects:
0.4.0 - 2026-01-05
Added
Permission Introspection Module: New
AshGrant.Introspectmodule for runtime permission queriesactor_permissions/3- Admin UI: Display all permissions with their status for an actoravailable_permissions/1- Permission management: List all possible permission combinationscan?/4- Debugging: Simple check returning:allowor:denywith detailsallowed_actions/3- API response: List allowed actions (with optional:detailedmode)permissions_for/3- Raw access to permission strings from resolver- All functions support
:contextoption for resolver context
Instance Permission Read Support: Instance permissions now work with read actions (
filter_check/1)AshGrant.Evaluator.get_matching_instance_ids/3extracts instance IDs from permissionsFilterCheckcombines RBAC scopes with instance ID filters using OR logic- Enables Google Docs-style sharing where specific resources are shared with specific users
- Example:
"doc:doc_abc123:read:"allows reading the specific document
0.3.1 - 2025-01-05
Added
Scope Descriptions: Optional
descriptionfield for scopes in the DSLscope :own, [], expr(author_id == ^actor(:id)), description: "Records owned by the current user"AshGrant.Info.scope_description/2to retrieve scope descriptions programmatically- Descriptions are displayed in
explain/4output for better debugging
Authorization Debugging with
explain/4: NewAshGrant.explain/4function for debugging authorization decisions- Returns
AshGrant.Explanationstruct with detailed decision info - Shows matching permissions with metadata (description, source)
- Shows all evaluated permissions with match/no-match reasons
- Includes scope information from both permissions and DSL definitions
AshGrant.Explanation.to_string/2for human-readable output with ANSI colors
- Returns
New Modules:
AshGrant.Explanation- Struct for authorization decision explanationsAshGrant.Explainer- Builds detailed authorization explanations
0.3.0 - 2025-01-04
Added
Permission Metadata:
AshGrant.PermissionInputstruct for permissions with metadatadescription- Human-readable description of the permissionsource- Where the permission came from (e.g., "role:admin")metadata- Additional arbitrary metadata as a map
Permissionable Protocol:
AshGrant.Permissionableprotocol for converting custom structs to permissions- Implement for your own structs to return them directly from resolvers
- Default implementations for
BitString,PermissionInput, andPermission
Instance Permissions with Scopes (ABAC): Instance permissions now support scope conditions
doc:doc_123:update:draft- Update only when document is in draft statusdoc:doc_123:read:business_hours- Access only during business hoursinvoice:inv_456:approve:small_amount- Approve only below threshold- Scopes are now treated as "authorization conditions" rather than just "record filters"
- Empty scopes (trailing colon) remain backward compatible ("no conditions")
New Evaluator Functions:
get_instance_scope/3- Get the scope from a matching instance permissionget_all_instance_scopes/3- Get all scopes from matching instance permissions
Context Injection for Testable Scopes: Scopes can now use
^context(:key)for injectable valuesscope :today_injectable, expr(fragment("DATE(inserted_at) = ?", ^context(:reference_date)))scope :threshold, expr(amount < ^context(:max_amount))- Enables deterministic testing of temporal and parameterized scopes
- Values are passed via
Ash.Query.set_context(%{reference_date: ~D[2025-01-15]})
Changed
- Documentation: Clarified that scope represents an "authorization condition" that can apply to both RBAC and instance permissions, enabling full ABAC (Attribute-Based Access Control)
0.2.2 - 2025-01-02
Fixed
- Documentation: Removed deprecated
owner_fieldfrom README examples - Documentation: Added note that instance permissions currently only work with write actions (
check/1)
Changed
- Tests: Enabled previously skipped "own" scope update tests that now pass
0.2.1 - 2025-01-01
Added
- Multi-tenancy Support: Full support for Ash's
^tenant()template in scope expressionsscope :same_tenant, expr(tenant_id == ^tenant())now works correctly- Tenant context is passed through to
Ash.Expr.eval/2 - Smart fallback evaluation when Ash.Expr.eval returns
:unknown
- TenantPost test resource: Demonstrates multi-tenancy scope patterns
Deprecated
owner_fieldDSL option: This option is deprecated and will be removed in v1.0.0. Use explicit scope expressions instead:
The fallback evaluation now extracts the field from the scope expression directly.# Instead of: owner_field :author_id # Use: scope :own, expr(author_id == ^actor(:id))
Improved
- Fallback expression evaluation: Smarter handling when
Ash.Expr.evalcan't evaluate- Analyzes filter to detect
^tenant()and^actor()references - Automatically extracts actor field from the filter expression (no
owner_fieldneeded) - Proper tenant isolation for write actions
- Analyzes filter to detect
0.2.0 - 2025-01-01
Added
- Default Policies: New
default_policiesDSL option to auto-generate standard policiesdefault_policies trueor:all- Generate both read and write policiesdefault_policies :read- Only generate filter_check policy for read actionsdefault_policies :write- Only generate check policy for write actions- Eliminates boilerplate policy declarations for common use cases
- Transformer:
AshGrant.Transformers.AddDefaultPoliciesgenerates policies at compile time - Info helper:
AshGrant.Info.default_policies/1to query the setting
Improved
- Expression evaluation: Now uses
Ash.Expr.eval/2for proper Ash expression handling- Full support for all Ash expression operators (not just
==andin) - Proper actor template resolution (
^actor(:id),^actor(:tenant_id), etc.) - Proper tenant template resolution (
^tenant()) - Handles nested actor paths automatically
- Full support for all Ash expression operators (not just
- Code quality: Removed ~60 lines of custom expression handling in favor of Ash built-ins
DSL Configuration (Updated)
ash_grant do
resolver MyApp.PermissionResolver # Required
default_policies true # NEW: auto-generate policies
resource_name "custom_name" # Optional
scope :all, true
scope :own, expr(author_id == ^actor(:id))
scope :published, expr(status == :published)
end0.1.0 - 2025-01-01
Added
- Unified Permission Format: New 4-part permission syntax
resource:instance_id:action:scope- RBAC permissions:
blog:*:read:all(instance_id =*) - Instance permissions:
blog:post_abc123:read:(specific instance) - Backward compatible with legacy 2-part and 3-part formats
- RBAC permissions:
- Scope DSL: Define scopes inline within resources using the
scopeentityscope :all, truescope :own, expr(author_id == ^actor(:id))scope :published, expr(status == :published)- Scope inheritance with
scope :own_draft, [:own], expr(status == :draft)
- Deny-wins semantics: Deny rules always override allow rules
- Wildcard matching:
*for resources/actions,read*for action prefixes - Two check types:
AshGrant.filter_check/1for read actions (returns filter expression)AshGrant.check/1for write actions (returns true/false)
- Property-based testing: 34 property tests for edge case discovery
- Comprehensive test coverage: 211 total tests (19 doctests + 34 properties + 158 unit tests)
DSL Configuration
ash_grant do
resolver MyApp.PermissionResolver # Required
resource_name "custom_name" # Optional
# Inline scope definitions (new!)
scope :all, true
scope :own, expr(author_id == ^actor(:id))
scope :published, expr(status == :published)
endBehaviours
AshGrant.PermissionResolver- Resolves permissions for actorsAshGrant.ScopeResolver- Legacy: translates scopes to Ash filters (deprecated in favor of scope DSL)
Modules
| Module | Description |
|---|---|
AshGrant | Main extension with check/1 and filter_check/1 |
AshGrant.Permission | Permission parsing and matching |
AshGrant.Evaluator | Deny-wins permission evaluation |
AshGrant.Info | DSL introspection helpers |
AshGrant.Check | SimpleCheck for write actions |
AshGrant.FilterCheck | FilterCheck for read actions |