Connector Access Migration — Operator Runbook
Moves connector access off the legacy connector_access_policy table onto the
uniform polymorphic model (installed_extension + extension_access_policy).
This is the one-shot, deploy-time, operator-run step (the autonomy boundary
— the migration is authored + tested against a throwaway schema, but applying it
to production is an operator action under the release-tag-gated deploy).
Preconditions
Section titled “Preconditions”- The migration code is deployed (the broadened
resource_kindCHECK ships viabuildCreateStoreSchemaQueriesoncinatra setup, and via migration 640). - A fresh backup exists:
cinatra backup create(defensive — the migration is non-destructive, but back up before any prod data migration). - Optionally
cinatra backup export-api-configs(connectors touched; no credential schema change, but access rows move).
-
Broaden the CHECK constraints (idempotent; also applied by setup):
Terminal window SUPABASE_DB_URL=<prod> SUPABASE_SCHEMA=<schema> \npx tsx src/lib/migrations/640-extension-access-kinds.tsExpect:
_kind_check_v2(7-kind list) added onextension_co_owners+extension_access_policy; re-run is a no-op. -
Backfill connector access (idempotent, transactional, race-safe):
Terminal window SUPABASE_DB_URL=<prod> SUPABASE_SCHEMA=<schema> \npx tsx src/lib/migrations/641-connector-access-to-polymorphic.tsExpect log:
N legacy connector_access_policy row(s) to migrate→installed_extension created: N, connector policies written: N. -
Verify counts:
-- every legacy row has a canonical install + policySELECT count(*) FROM <schema>.connector_access_policy; -- legacy totalSELECT count(*) FROM <schema>.extension_access_policyWHERE resource_kind = 'connector'; -- should match-- mapping fidelity (expect 0)SELECT count(*) FROM <schema>.connector_access_policy lJOIN <schema>.installed_extension ieON ie.organization_id=l.org_id AND ie.owner_level='organization'AND ie.owner_id=l.org_id AND ie.package_name=l.package_id AND ie.kind='connector'JOIN <schema>.extension_access_policy pON p.resource_kind='connector' AND p.resource_id=ie.idWHERE (p.policy->>'runDataVisibility') <> l.visibility; -- mismatches -
Smoke: as an org member, confirm
workspace-visibility connectors are visible andadmin-visibility connectors are hidden (admin-only) on/connectors. The runtime now reads the canonical model; the legacy table is only the absence-only fallback (a one-time[connector-policy] using legacy connector_access_policy fallbackwarning means an org had no canonical row).
Rollback
Section titled “Rollback”The migration is additive (it never deletes legacy rows). To roll back the code, redeploy the prior tag; the shim reads legacy when no canonical row exists. The pre-deploy backup is the hard rollback.
Follow-up (separate, after verification)
Section titled “Follow-up (separate, after verification)”Once production is verified on the canonical model, a later removal migration can
DROP connector_access_policy and delete the absence-only fallback branch in
src/lib/connector-policy.ts. Tracked as a deprecation.