Documentation · Operations

Supabase Runbook: Control-Plane Postgres & RLS

The Praxion control plane stores its state in a Postgres database hosted on Supabase. It holds workspaces, telemetry fingerprints, billing units, and the audit chain — never your row data. This runbook documents the security model (forced Row Level Security), the agent-fed pattern, and the operations you may need to perform.

Security model: forced RLS

All nine application tables run with ENABLE + FORCE ROW LEVEL SECURITY. Forcing matters: ENABLE alone lets the table owner bypass RLS. With FORCE, even the owner is filtered, and the service_role is the only credential that can operate through policies:

ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE workspaces FORCE ROW LEVEL SECURITY;

Policies are scoped to the requesting organization. The Settings screen states it plainly: org_id is the isolation axis Row Level Security acts on, and a session with no organization can see nobody's data. That is why the Clerk org → workspace mapping (see the Clerk runbook) is load-bearing — RLS reads it on every query.

Who writes what

PathAccessNotes
Dashboard / APIRLS-filteredTenant-scoped rows only, via session workspace
Ingest workerservice_roleWrites telemetry batches; input is validated before insert
Agent-fed tablesapplication-filteredRows pre-filtered server-side, never client-trusted
Audit logappend-onlyHash-chained; no UPDATE/DELETE policy exists

The agent-fed pattern

The agent never talks to Supabase directly. Its batches arrive at the ingest endpoint, are validated and rate-limited, and are written by the worker — application-filtered on purpose. No client-supplied payload can reach a table without passing through the worker's validation, so a compromised agent cannot inject rows into another tenant's namespace.

Migrations

Schema changes are applied as numbered migrations (e.g. mig 0008). Each migration is a transaction; the audit chain is not affected by DDL, but the audit_events table records the migration ID for traceability. Rules of the road:

  • Run migrations with a dedicated migrator role, never the app role.
  • Back up before DDL on semantic, billing, or audit_events — the tables the compliance story depends on.
  • Test on a staging project first; Supabase branching makes this cheap.

Common operations

Verify RLS is actually forced

SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class
WHERE relrowsecurity;

Every row must show relforcerowsecurity = t. If one does not, apply the FORCE statement above and re-run the check.

Inspect tenant scope of a row

SELECT workspace_id, count(*)
FROM telemetry_fingerprints
GROUP BY workspace_id;

Use this to confirm data is landing under the right workspace after an ingest incident.

Restore from backup

Supabase point-in-time recovery is the supported path. Restore to a staging project, verify the audit chain hash validates end-to-end, then promote. The chain validation is the acceptance test — if it does not validate, do not promote.

Troubleshooting

  • RLS "infinite recursion" errors — usually a policy that queries the same table it filters. Check the policy body for self-references.
  • Rows visible across workspaces — the session workspace_id is not being set (login flow), or a table lost its FORCE. Run the verification query above.
  • Audit chain breaks after a manual edit — expected. The chain is append-only by design; manual UPDATE invalidates it and raises an alert. Restore from backup rather than patching rows.
Golden rule: the control plane is tenant-isolated by construction. If you ever need to bypass RLS, you are on the wrong path — restore, don't override.

Isolation you can verify.

Audit the chain, verify the RLS, and sleep well. Questions? Real humans reply within a day.

Contact support →