Skill v1.0.1
currentAutomated scan100/100+3 new
version: "1.0.1" name: ash-framework description: Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
Ash Framework Reference
Reference for Ash Framework in Phoenix/LiveView projects. Ash complements Phoenix/Ecto — LiveView, security, and OTP Iron Laws still apply. Only data access patterns shift toward Ash actions and domain code interfaces.
Iron Laws
- USE DOMAIN CODE INTERFACES — Never call
Ash.create/Ash.readdirectly in LiveViews or Controllers; use domain code interfaces:MyApp.Accounts.register_user()notAsh.create(User, attrs) - SET ACTOR/SCOPE AT QUERY PREP, NOT EXECUTION — Pass
actor:orscope:to
for_read/for_create/for_action (prep), NOT to Ash.read!/Ash.create! (execution); execution-level actor bypasses row-level policy evaluation. If project uses Ash.Scope, pass scope: consistently instead of bare actor: — do not mix styles
- GENERATORS FIRST — Before writing Ash code manually, run
mix ash.gen.resourceormix ash.gen.domainwith--yes; checkmix help ash.gen.<task>for options - CODEGEN AFTER RESOURCE CHANGES — Always run
mix ash.codegenafter modifying resources; this generates migrations from resource snapshots — never write AshPostgres migrations by hand - ACTIONS OVER FUNCTIONS — Put business logic in named actions, not domain functions; expose via code interfaces defined on the domain
- NEVER EDIT RESOURCE SNAPSHOTS —
priv/resource_snapshots/is owned exclusively bymix ash.codegen; manual edits corrupt migration tracking - NO DIRECT `Repo.*` IN ASH PROJECTS —
Repo.all/get/insertbypass Ash policies and notifications; use domain code interfaces. AnyRepocall in an Ash project is an escape hatch and must be documented
Quick Reference
Domain Code Interface Pattern
# Domain definitiondefmodule MyApp.Accounts douse Ash.Domainresources doresource MyApp.Accounts.User dodefine :register_user, action: :create, args: [:email, :password]define :get_user_by_email, action: :read, get_by: [:email]endendend# In LiveView/Controller — always via domain, never Ash.create directly{:ok, user} = MyApp.Accounts.register_user(email, password, actor: nil)user = MyApp.Accounts.get_user_by_email!(email, actor: current_user)
Authorization — Actor/Scope at Query Prep
# CORRECT — actor at query prep, policies evaluated per-rowMyApp.Post|> Ash.Query.for_read(:list_published, %{}, actor: current_user)|> Ash.read!()# CORRECT with Ash.Scope (carries actor + tenant + context; use if project adopts it)MyApp.Post|> Ash.Query.for_read(:list_published, %{}, scope: scope)|> Ash.read!()# WRONG — actor at execution bypasses row-level policy evaluationMyApp.Post|> Ash.Query.for_read(:list_published)|> Ash.read!(actor: current_user)
Ash.Scope — When the Project Uses It
Ash.Scope bundles actor + tenant + context into a single struct passed through actions. Implement Ash.Scope.ToOpts on a project-defined scope struct:
defimpl Ash.Scope.ToOpts, for: MyApp.Scope dodef get_actor(%{current_user: u}), do: {:ok, u}def get_tenant(%{current_tenant: t}), do: {:ok, t}def get_context(%{locale: l}), do: {:ok, %{shared: %{locale: l}}}def get_tracer(_), do: :errordef get_authorize?(_), do: :errorend
Detection: if the project has a Scope module implementing Ash.Scope.ToOpts, use scope: everywhere instead of bare actor:. Do NOT mix the two styles in the same codebase. See mix usage_rules.docs Ash.Scope for full protocol spec.
File Conventions (from mix ash.gen.*)
| File | Location | Behaviour | |
|---|---|---|---|
| Changes | lib/app/ctx/changes/name.ex | use Ash.Resource.Change | |
| Policy Checks | lib/app/ctx/checks/name.ex | use Ash.Policy.Check | |
| Custom Actions | lib/app/ctx/actions/name.ex | generic action logic | |
| Custom Types | lib/app/ctx/types/name.ex | use Ash.Type | |
| Validations | lib/app/ctx/validations/name.ex | use Ash.Resource.Validation |
Generator Workflow
mix ash.gen.resource MyApp.Accounts.User --yesmix ash.gen.domain MyApp.Accounts --yesmix ash.codegen # reads resource snapshots → generates migrationmix ash.migrate
Research
Prefer the highest-fidelity source available:
- Tidewave (exact version from
mix.lock):
`` mcp__tidewave__get_docs(module: "Ash.Resource") mcp__tidewave__get_docs(module: "AshPhoenix.Form") ``
- usage_rules (project-synced to your installed ash_* dep versions):
``bash mix usage_rules.search_docs "<topic>" -p ash -p ash_phoenix -p ash_postgres -p ash_authentication -p ash_oban mix usage_rules.docs Ash.Resource ``
- WebFetch hexdocs.pm (fallback when neither is available):
`` WebFetch(url: "https://hexdocs.pm/ash/Ash.Resource.html", prompt: "Extract module docs.") ``
If usage_rules is not configured, the SessionStart hook suggests how to install it.