btajudeen
Hi Zach, I am building a multi-tenant SaaS application using Ash v3 with a schema/context-based multitenancy strategy.
During tenant onboarding, I execute a synchronous form submission that successfully creates a Workspace (a global public resource). Immediately after, I kick off a background setup worker using Phoenix LiveView’s start_async/3 to provision the tenant.
Inside that async worker thread, before any multi-tenancy prefixing occurs, I need to update a global user record in the public schema to lock down their last_active_workspace_id. However, the query fails instantly.
Here is the code inside my async task process:
Accfarm.Shared.User
|> Ash.get!(workspace.owner_id, domain: Accfarm.SharedDomain, authorize?: false)
|> Ash.Changeset.for_update(:update, %{last_active_workspace_id: workspace.id})
|> Ash.update!(domain: Accfarm.SharedDomain, authorize?: false)
And heres my Debug log output
[debug] CREATE SCHEMA IF NOT EXISTS "org_green-valley-hub" [ ]
[debug] Accfarm.Shared.User.read: skipped query run due to filter being false"
đź”´ SYSTEM MULTI-TENANT PROVISIONING EXCEPTION: %Ash.Error.Invalid{
errors: [
%Ash.Error.Query.NotFound{
primary_key: %{id: "019e87e3-884d-7325-8a74-f938a1d373db"},
resource: Accfarm.Shared.User
}
]
}
My Resource Configurations:
Both Workspace and User reside in the public schema and are explicitly configured as global resources:
multitenancy do
global? true
end
Because authorize?: false is specified, I expected the policy engine to be completely bypassed. Why is Ash generating a filter being false condition on a global public resource read, and how do I execute a system-level override inside a background worker process to update this public data row?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security











Showing Posts 1 to 5- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
zachdaniel
btajudeen
"Thanks for the pointer, Zach! That makes total sense. I checked the logs and it is definitely a static filter rewrite preventing the database call entirely.
To help me debug what preparation or base filter is causing this, here is the complete
actions do ... endblock and attributes layout for myAccfarm.Shared.Userresource file:lib/accfarm/shared/user.ex
multitenancy do
global? true
end
attributes do
uuid_v7_primary_key :id
attribute :email, :ci_string, allow_nil?: false
attribute :last_active_workspace_id, :uuid
attribute :system_role, :atom, default: :user, constraints: [one_of: [:user, :admin]]
timestamps()
end
actions do
defaults [:destroy, :update]
This is the action being invoked by Ash.get!/3
read :read do
primary? true
Are there implicit global preparations or multi-tenancy filters
injected here under the hood by AshPostgres when strategy :context is active elsewhere?
end
end
policies do
policy always(), do: authorize_if(always())
end
Given that this resource has
global? trueenabled, does theAshPostgres.DataLayerinject implicit tenant routing filters onto primary read actions if other domains in the application use a context/schema multi-tenancy strategy? How can I inspect or trace the exact statically generated expression that is forcingfilter being falseon this read path?"zachdaniel
Typically this would include a multitenancy attribute, i.e
If you’re just saying “it’s not multi tenant” then you’d leave the block out. Its possible that we have an issue where it doesn’t check that there is an attribute and tries to do something silly like
nil == nil(i.e no attribute, no tenant)?btajudeen
"You nailed it perfectly, Zach! That is exactly what was happening.
Both
UserandWorkspaceare global public resources that do not have an isolation column attribute because they live exclusively outside the tenant schema boundaries. I had declared a blankmultitenancy do global? true endblock inside them thinking it was required to declare them as global.Once that block is evaluated in a background async task without a process tenant set, it triggers that hidden
nil == nilstate mismatch, which appends a hardwhere falseonto the query builder path [debug].Removing the
multitenancyblock entirely from those global public files fixes the query compilation rewrite instantly [debug]! Thanks for the brilliant architectural insight."zachdaniel
Would you mind opening an issue about this on the
ashrepo?