fix: append default policies + break domain↔resource compile cycle#4
Merged
C-Sinclair merged 6 commits intomainfrom Apr 21, 2026
Merged
Conversation
Ash bypass policies only short-circuit policies that come after them. add_entity defaults to prepend, which placed AshGrant's generated policies before user-defined bypass policies, making bypasses ineffective. Switch to type: :append so user-defined bypass policies are evaluated first and can properly short-circuit AshGrant's permission checks.
…face A resource using `AshGrant` plus a domain that uses `AshGrant.Domain` AND declares a `code_interface do define … end` block deadlocked on compile: the resource's `MergeDomainConfig` transformer forced the domain to compile, while the domain's code_interface transformer was waiting for the resource. Move domain config merging from a compile-time transformer to runtime `AshGrant.Info` calls (`resolver/1` falls back to the domain, `scopes/1` merges resource + domain scopes with resource precedence). Convert the compile-time presence checks to Spark verifiers, which run post-compile and can safely reach into the domain. Spark surfaces verifier errors as compile warnings, so `AshGrant.Check.resolve_permissions/3` also raises a clear `ArgumentError` when no resolver is configured at authorization time. Adds a regression test (`code_interface_cycle_test.exs`) that pairs `AshGrant` on a resource with `AshGrant.Domain` + `code_interface` on the domain. This case deadlocked on the previous code.
The previous two commits unintentionally dropped content that is on team-alembic/ash_grant:main: - AshGrant.Transformers.AddArgumentResolvers was removed from the extension's transformer list. That transformer wires up every `resolve_argument` declaration; without it the DSL entity exists but silently no-ops, breaking any resource that relies on argument-based scopes. - AshGrant.Transformers.ValidateScopeThroughs was also removed from the list, dropping compile-time validation for scope_through entities. - Moduledoc examples in lib/ash_grant.ex were rewritten `:always` → `:all`, undoing the jhlee111#84 rename in docs only (runtime was unchanged, but the docs drifted from the rest of the codebase). - AshGrant.Verifiers.ValidateScopes lost the `instance_key` attribute check that main's equivalent transformer did, and added an unrelated "scope `:all` is missing" warning that collides with main's `:always` convention. Restore the two transformers in the extension's pipeline, revert the moduledoc examples to `:always`, port main's `instance_key` validation into the new verifier, and drop the stray common-scope warning. Test fixtures that used `:all` switch to `:always`. The intended fixes — append policies, runtime domain merge, verifier conversion, runtime resolver guard — are preserved.
2 tasks
C-Sinclair
added a commit
that referenced
this pull request
Apr 21, 2026
Resolves conflicts from two changes landed on main: - #2 removed scope inheritance (no impact here — grants don't use scope inheritance; tests still combine conditions inline with `and`). - #4 broke the compile-time cycle between resource and domain by removing the MergeDomainConfig transformer and the ValidateResolverPresent transformer, moving resolver-merge logic into AshGrant.Info.resolver/1 at runtime and turning ValidateResolverPresent into a verifier. Adjustments to this branch: - Dropped lib/ash_grant/transformers/validate_resolver_present.ex — the file was deleted on main (replaced by a verifier). My earlier explicit before?/after? guards in that file were no longer needed. - Reintegrated NormalizeGrants and SynthesizeGrantsResolver into the new transformer list (main-first ordering, no references to the now-gone MergeDomainConfig). - Added ValidateGrantReferences to the verifier list alongside the new ValidateResolverPresent and ValidateScopes verifiers. - Cleaned before?/after? in NormalizeGrants and SynthesizeGrantsResolver to reference only transformers that still exist. Full suite still green (1089 tests, 1 pre-existing unrelated failure). Credo clean on all new files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #1 by @barnabasJ. Original commits preserved; one follow-up commit on top restores pipeline entries and naming that were inadvertently dropped in the original branch.
Summary
Two independent fixes surfaced while adopting
ash_grantin a real codebase (ARCC Center — Ash 3.x, ~57 resources, domain-levelcode_interfaceblocks).1.
cfce2b4— append default policies after user-defined onesAshGrant.Transformers.AddDefaultPolicieswas prepending its generated policies. Ashbypasspolicies only short-circuit policies that come after them, so user-definedbypass action_type(:create)blocks (e.g. "allow public registration fornilactor") never fired — ash_grant's permission check ran first and denied.Flip the builder call to
type: :appendso user-defined policies (including bypasses) are evaluated first and can properly short-circuit ash_grant's generated checks.2.
e56b814— break domain↔resource compile cycle with domain-levelcode_interfaceCombining
AshGranton a resource withAshGrant.Domain+ acode_interface do define … endblock on the domain deadlocked at compile:MergeDomainConfigtransformer forced the domain to compile (to read domain-levelash_grantDSL).code_interfacetransformer was waiting for the resource to finish compiling.Fix:
MergeDomainConfigtransformer. Move resolver/scope merging from compile-time into runtimeAshGrant.Infocalls:resolver/1falls back to the domain;scopes/1merges resource + domain scopes with resource precedence.ValidateResolverPresentandValidateScopesbecome verifiers — they run post-compile and can safely reach into the domain without creating a cycle. Spark surfaces verifier errors as compile warnings, soAshGrant.Check.resolve_permissions/3also raises a clearArgumentErrorat authorization time if no resolver is configured.test/ash_grant/code_interface_cycle_test.exspairsAshGranton a resource withAshGrant.Domain+code_interfaceon the domain — this case deadlocked on the previous code.3.
f637749— restore transformers and scope naming dropped during rebaseThe previous two commits unintentionally dropped content already on main:
AshGrant.Transformers.AddArgumentResolverswas removed from the extension's transformer list. That transformer wires up everyresolve_argumentdeclaration; without it the DSL entity exists but silently no-ops.AshGrant.Transformers.ValidateScopeThroughswas also removed, dropping compile-time validation forscope_throughentities.lib/ash_grant.exwere rewritten:always→:all, undoing the refactor: rename :all scope to :always across codebase jhlee111/ash_grant#84 rename in docs only.AshGrant.Verifiers.ValidateScopeslost theinstance_keyattribute check that main's equivalent transformer did, and added an unrelated "scope:allis missing" warning that collides with main's:alwaysconvention.This commit restores the two transformers in the pipeline, reverts the moduledoc examples, ports the
instance_keyvalidation into the new verifier, and drops the stray common-scope warning. Test fixtures that used:allswitch to:always.Test plan
mix test— 1062 tests, 1 failure, and that failure (default_field_policies_test.exs:52) reproduces onmainunchanged, so it is pre-existing and unrelated.mix compile(the expected "no resolver configured" warning onNoResolverPostis emitted by the verifier, as designed).