One tenant per transaction.
The API connects as a non-owner PostgreSQL role without BYPASSRLS. postgres.Store.RunTenant installs a signed, transaction-local tenant binding. Application tables opt in by comparing their tenant column to amsonia.tenant_id().
The host still authenticates the request, chooses the active tenant from trusted server-side context, loads the resource, and checks the required Amsonia permission. RLS is the final storage boundary, not a replacement for authorization.
Filter reads. Reject writes.
ALTER TABLE app.invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.invoices FORCE ROW LEVEL SECURITY;
CREATE POLICY invoice_tenant_isolation ON app.invoices
USING (tenant_id = amsonia.tenant_id())
WITH CHECK (tenant_id = amsonia.tenant_id());USING hides rows outside the signed tenant. WITH CHECK rejects inserts and updates claiming another tenant. Directly setting PostgreSQL session variables does not produce a valid signature.
Run two tenants against one table.
The checked-in example writes one invoice for Acme and one for Globex. Its select intentionally has no tenant predicate. Each signed transaction sees one row, a cross-tenant insert fails RLS, and the PostgreSQL integration test proves a forged context sees zero rows.
make demo
# Apply examples/business-data-rls/schema.sql as the owner.
set -a
. .amsonia/local.env
set +a
go run ./examples/business-data-rlsKeep privileged paths separate.
- Never run the API with the migration owner DSN.
- Never accept a tenant header and issue raw SQL outside the signed transaction.
- Grant the runtime role only the table operations it needs.
- Keep binding secrets out of browser code, logs, and source control.