# Supabase row recovery

This connector protects selected row updates in a Supabase database. Rewind's hosted API and account history run separately from the connected data. Installing this connector alone does not host Rewind. The public hosted app is at https://rewind.kanishq.dev; the local edition uses Node/SQLite.

## First test in a dedicated project

1. Create a separate **Rewind** project on the Free plan if your account has a free slot. Do not install into another app's project for the initial test. No upgrade is required by this connector.
2. Run `install.sql` in that project's SQL editor. It creates connector functions and an empty table registry; it does not register or alter any application table. Re-running the same installer is supported.
3. Run `demo-table.sql` once. This explicitly creates and registers the disposable `rewind_demo_customers` table. It has no anonymous or authenticated-user access policies. Use the project's server-side secret key for this fixture. Run `smoke-test.sql` to check capture, recovery, stale-version rejection and anonymous-access restrictions as the service role; its test writes are rolled back.
4. In Rewind, open **Connect n8n**, choose **A Supabase row**, enter this development project’s URL, and choose **Prepare templates**. Download both workflows and import each with n8n’s **Import from File**. Both addresses are prefilled. If you use the raw repository exports instead, set both addresses in **Configure endpoints** yourself. Leave the demo alias, row ID and patch for the first test.
5. Configure three n8n Header Auth credentials: Rewind recording key (`Authorization: Bearer …`), Rewind worker key (`Authorization: Bearer …`), and a Supabase **server-side secret key** (`apikey: sb_secret_…`). Keep secrets in n8n's credential store. A Supabase secret key bypasses row-level security; never send it to Rewind or put it in browser code, workflow JSON, Git or chat.
6. Select the Supabase credential on **Read row** and **Capture update**, and the Rewind recording credential on **Record operation**. **Run once** changes the demo row to inactive / 3 seats and records the actual returned snapshots.
7. Open Rewind's **Workflows**, inspect the operation, and approve its undo.
8. Import `n8n-supabase-recovery.json`, configure the same two origins, select the Rewind worker credential on **Claim approved job** and **Report outcome**, and the Supabase credential on **Read current resource** and **Conditional restore**. Run it manually. Verify both Rewind's reported outcome and the demo row in Supabase.
9. Repeat the capture, then edit the demo row in Supabase before approving undo. Recovery should report a conflict and preserve the newer edit. Only publish the recovery schedule after these manual checks.

Both exports are inactive and contain no credentials. The capture workflow changes data: it is an example for one row, not a batch processor or transparent wrapper around arbitrary n8n Supabase nodes. n8n Cloud needs a publicly reachable HTTPS Rewind deployment. Local Docker n8n can use `http://host.docker.internal:4173` for Rewind.

The provider update and Rewind recording are separate transactions. If recording fails, retry **only Record operation** with its saved payload and idempotency key. Re-running the entire workflow repeats the forward action. If the update response is lost, inspect the row; do not automatically repeat the update. Retain captured output in your workflow until recording succeeds. This version has no durable database outbox, so a successful update whose response/output is lost can remain unrecorded.

## Enable an application table later

Only the database owner registers tables. First review existing triggers and side effects. Registration adds one `rewind_revision` UUID column and a trigger that rotates it on every insert/update. It does not change table grants or RLS policies. The table must already have RLS enabled, a single UUID primary key named `id`, and 1–20 ordinary text/varchar, boolean, smallint or integer columns to opt in:

```sql
SELECT rewind_connector.register_table(
  'customers', 'public.customers'::regclass,
  ARRAY['status','seats']::name[]
);
```

The alias is a fixed lower-case identifier. It becomes the resource prefix, such as `customers:d81098bd-8f67-4674-a1ba-007019b9dcc0`. Do not register production tables until you have tested their RLS, triggers, and recovery behavior. Registration can lock/rewrite a populated table. The connector cannot undo sent messages, trigger side effects, changes to other rows, insertions or deletions. Privileged changes that disable or replace its revision trigger violate the recovery contract.

## RPC contract

All RPCs use POST to `/rest/v1/rpc/<name>` on the **fixed project origin** configured in the worker:

| RPC | JSON arguments | Result |
| --- | --- | --- |
| `rewind_read_row` | `p_alias`, `p_id` | `{record, version}` containing only opted-in fields |
| `rewind_capture_update` | `p_alias`, `p_id`, `p_expected_version`, `p_patch` | `{before, after, version}` from the successful update |
| `rewind_restore_row` | `p_alias`, `p_id`, `p_expected_version`, `p_before`, `p_after` | `{before, after, version}` from restoration |

Record the capture result in Rewind with `kind: "supabase_row"`, resource `alias:uuid`, and `afterVersion` equal to the returned UUID **wrapped in double quotes**. The generic recording template accepts this kind too. Never invent snapshots or substitute a timestamp for the returned revision.

Capture and restore lock the row, compare the revision, validate fields, and update within one PostgreSQL transaction. Restore also compares the current fields with the recorded `after` snapshot. Every write changes the revision, so unrelated edits, no-op writes, and changes that are later changed back all block an older undo. Only existing non-null scalar values of the same JSON type are supported. A trigger that transforms the requested fields causes the transaction to fail instead of recording inaccurate snapshots.

Functions use **SECURITY INVOKER** and an empty search path. Anonymous execution is revoked. The private table registry has RLS enabled, explicit read-only policies for authenticated/service roles, and owner-only registration. Authenticated callers retain their existing application-table permissions and RLS rules. Supabase's secret/service-role keys inherently bypass RLS; these remain powerful server credentials even though this connector allows only registered fields. The installer does not make such keys suitable for an untrusted client.

The Node worker can use a publishable key with a separate, valid user JWT in `SUPABASE_ACCESS_TOKEN` to respect that user's RLS policies. JWT renewal must be handled by the caller. The supplied n8n templates use a new server-side `sb_secret_…` key; legacy JWT keys or authenticated-user access require explicitly configuring the appropriate `Authorization` header as well.

## Custom Node worker

Set `REWIND_URL`, `REWIND_WORKER_KEY`, `SUPABASE_URL`, and `SUPABASE_KEY` in your local environment, then run:

```sh
node integrations/worker.mjs
```

The worker claims only configured adapter kinds. Legacy HTTP workers default to `conditional_http` and leave Supabase jobs queued. Supabase-only workers claim `{"kinds":["supabase_row"]}`. You may configure both adapters on the Node worker.

No provider mutation is automatically retried. A lost response, malformed success or 5xx is reported as **Outcome unknown** and needs inspection. The exact completion report can be retried without repeating the provider write.

## Local connection and live demo check

With Rewind running locally and the demo SQL already installed, use a dedicated Rewind test account whose email ends in `@example.invalid`. The account must have no queued or leased work. Keep other workers stopped during this check.

```sh
SUPABASE_URL=https://YOUR_PROJECT.supabase.co node scripts/connect-supabase.mjs
```

Open the one-time loopback URL printed by the script. Enter the test account's existing login and the project's existing `sb_secret_…` key into the masked fields. The form names the exact project and explains the test before submission. It uses no external assets, accepts only its own host and same-origin requests with a random token, rejects repeat submissions, and expires after 20 minutes.

The check starts only when the demo row is active / 5 seats. It records and explicitly approves two test operations: a successful undo and a later-edit conflict. It verifies the real provider values after each, conditionally restores the fixture, and checks an idle worker makes no change. Any unexpected or uncertain result stops the check without retrying provider writes or doing blind cleanup. Inspect the saved evidence and the demo row before trying again.

The script stores `worker.env` with mode `0600` inside a mode `0700` directory under ignored `data/supabase-live-<timestamp>/`. It stores neither the Rewind password nor browser cookies. The Supabase key remains a privileged project credential; use only the dedicated test project here. The temporary recording key is revoked after the run; the scoped worker key remains configured and expires after 90 days. A credential-free `verification.json` retains snapshots, operation IDs and audit evidence, including the exact recording payload if recording fails after a capture.

Run the saved connection on demand with:

```sh
node --env-file=data/supabase-live-TIMESTAMP/worker.env integrations/worker.mjs
```

This processes at most one approved job and exits, using the Node worker's persistent completion journal. For continuous processing, use `integrations/worker-daemon.mjs` with the same environment file. The [macOS service controller](../../deployment/README.md#local-background-services-on-macos) can run it independently of the terminal. The setup form itself does not install a background service, publish an n8n schedule, or host Rewind publicly. Stop the one-time setup process after verification.

To explicitly test an already-running background worker, run `scripts/test-background-supabase.mjs` with the provider environment plus `REWIND_TEST_EMAIL` and `REWIND_TEST_PASSWORD` for the existing dedicated test account. It only operates on the disposable demo row. The check records and approves the two demo operations, waits for the background worker's actual reports, verifies provider values and conditionally cleans up. It never invokes a worker itself. A failure stops the check; inspect the queue and row before retrying, since a queued job may still run. Evidence is saved privately under `data/background-check-<timestamp>/`.

## Validation and cost boundaries

`npm run test:postgres` runs the installer and transaction checks in a disposable local PostgreSQL 16 container. It checks RLS isolation, privileges, concurrent restoration, conflicts, replay, changed-back values, deletion/reinsertion and field restrictions. The test container has no network or published port and uses no cloud account.

`npm run test:n8n` runs the exported n8n data paths in n8n 2.40.7 against disposable local HTTP fixtures. It also publishes the recovery workflow and verifies that its actual one-minute schedule restores approved changes and protects later edits. The Supabase fixture checks the RPC wire contract; the separate PostgreSQL suite exercises the actual SQL. The dedicated hosted project also passed the live Node worker check described above; see [deployment status](../../deployment/supabase-status.md). The editor's file-import/publish flow and n8n templates against the hosted project remain separate, unverified checks.

Local development has no paid API dependency. Supabase's Free plan has account/project limits, inactivity pausing and no automatic backups. The hosted Rewind app runs on Cloudflare Pages and Supabase. A separate local Rewind instance needs a persistent host for public access. Consult [Supabase pricing](https://supabase.com/pricing) before provisioning; never upgrade merely to complete this test. The default Supabase email sender is restricted to project-team recipients and is not a public-signup email service ([SMTP documentation](https://supabase.com/docs/guides/auth/auth-smtp)).
