-- +goose Up -- Reverts the audit_logs row level security added in 0064. It broke authentication. -- -- What went wrong: Postgres applies a SELECT policy to RETURNING. The audit insert uses RETURNING, and a -- platform event such as a login is written with organization_id NULL, before any tenant is known. The -- policy compared organization_id to the request GUC, and NULL = anything is NULL rather than true, so -- the row was inserted and then filtered out of its own RETURNING clause. The scan found no row and the -- login path panicked on the nil dereference. Every login on the platform returned 500. -- -- The finding that prompted it is real: audit_logs is tenant scoped, the app role holds SELECT, and it is -- the largest cross tenant surface in the schema protected only by the convention that every query -- remembers an organization predicate. But it cannot be closed with a naive policy, because this table -- deliberately holds two different KINDS of row: -- -- * tenant events, with an organization_id, which must be readable only by that tenant, and -- * platform events, with organization_id NULL (a failed login, a lockout, a session rejection), which -- belong to no tenant and must remain writable and readable before any tenant context exists. -- -- A correct policy has to express both, and it has to be written against a read path that does not depend -- on RETURNING seeing rows the policy hides. That is a considered change with its own verification, not a -- line in a batch migration, so the table returns to its previous posture and the finding stays open and -- documented rather than half fixed in a way that takes the product down. DROP POLICY IF EXISTS audit_logs_tenant_read ON audit_logs; DROP POLICY IF EXISTS audit_logs_insert_any ON audit_logs; ALTER TABLE audit_logs NO FORCE ROW LEVEL SECURITY; ALTER TABLE audit_logs DISABLE ROW LEVEL SECURITY; -- +goose Down -- Deliberately empty. Re enabling the policy is what broke login; a down migration must not restore a -- known outage. The forward fix, when it is written, gets its own migration. SELECT 1;