critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

IntegrationCredentialVaultClient is an abstract interface with a SupabaseCredentialVaultClient implementation
storeCredentials(String integrationId, Map<String, String> credentials) calls the Supabase RPC 'vault_store_integration_credentials' and returns CredentialReference(vaultSecretId: String)
retrieveCredentials(CredentialReference ref) calls RPC 'vault_retrieve_integration_credentials' and returns Map<String, String> — method is restricted to server-side / background use and must not be called from UI layer
rotateCredentials(CredentialReference ref, Map<String, String> newCredentials) replaces vault secret and returns a new CredentialReference with updated vaultSecretId
deleteCredentials(CredentialReference ref) calls RPC 'vault_delete_integration_credentials' and returns void
No plaintext credential value ever appears in: print statements, debugPrint, log(), toString(), or any exception message
All RPC errors are caught and rethrown as CredentialVaultException with a safe (non-sensitive) error message
CredentialReference is an @immutable class containing only vaultSecretId (String) and createdAt (DateTime)
storeCredentials validates that the credentials Map is non-empty and all values are non-empty strings before making the RPC call
The client handles Supabase session expiry by propagating the auth error without retrying automatically

Technical Requirements

frameworks
Dart
Flutter
Riverpod
apis
Supabase RPC: vault_store_integration_credentials(integration_id, credentials_json)
Supabase RPC: vault_retrieve_integration_credentials(vault_secret_id)
Supabase RPC: vault_delete_integration_credentials(vault_secret_id)
Supabase Vault (pgsodium extension)
data models
CredentialReference
CredentialVaultException
performance requirements
storeCredentials must complete within 5 seconds including network round-trip
retrieveCredentials is expected to be called only in background sync contexts — not on UI thread critical path
security requirements
NEVER log credential values — use a custom CredentialMap wrapper that overrides toString() to return '[REDACTED]'
Do not cache credential values in memory after the RPC call returns
retrieveCredentials must only be callable from authenticated service-role context or server-side function — document this constraint clearly
CredentialReference must not contain or expose the actual credential values
Validate vaultSecretId format (UUID) before making RPC calls to prevent injection

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Place in lib/infrastructure/integrations/credential_vault/. Create a CredentialMap class that wraps Map and overrides toString() to return '[REDACTED]' — use this throughout the client instead of raw Map. The RPC functions are PostgreSQL functions that proxy to Supabase Vault (pgsodium) — they accept/return JSON. For storeCredentials, serialize credentials to JSON string before passing to RPC.

The vault secret ID returned by the store RPC is what gets persisted in organization_integrations.credential_vault_id. Never persist the full credentials in the Supabase table. Register via Riverpod as Provider. Add a dart doc comment on retrieveCredentials explicitly warning: '// WARNING: Returns plaintext credentials.

Must only be called from background sync service, never from UI layer.'

Testing Requirements

Write unit tests using flutter_test with mocked Supabase RPC client. Test: (1) storeCredentials calls correct RPC function with serialized credentials and returns CredentialReference; (2) storeCredentials throws ArgumentError for empty credentials map; (3) retrieveCredentials calls correct RPC with vault secret ID; (4) rotateCredentials deletes old and stores new secret, returns new CredentialReference; (5) deleteCredentials calls delete RPC; (6) any RPC error (PostgrestException) is wrapped in CredentialVaultException with non-sensitive message; (7) CredentialReference.toString() does not contain any credential value; (8) CredentialMap.toString() returns '[REDACTED]'. Security-test: assert no credential value appears in any thrown exception message.

Component
Integration Credential Vault
data high
Epic Risks (3)
high impact medium prob technical

Supabase Vault API has limited documentation for Dart/Flutter clients; wrapping it correctly for credential rotation and secret reference management may require significant trial and error, delaying the vault component and blocking all downstream credential-dependent work.

Mitigation & Contingency

Mitigation: Spike the Vault integration in the first sprint using a minimal proof-of-concept (store, retrieve, rotate one secret). Document the API surface before building the full vault client. Identify any missing Dart SDK bindings early.

Contingency: If Supabase Vault is too complex, fall back to Supabase's encrypted column approach (pgcrypto) for credential storage as a temporary measure, with a planned migration path to Vault once the API is understood.

high impact low prob security

Incorrect RLS policy configuration on organization_integrations could allow org admins of one organization to read or modify another organization's integration credentials, creating a serious data breach and compliance violation.

Mitigation & Contingency

Mitigation: Write integration tests that explicitly attempt cross-org data access using different JWT tokens and assert 0 rows returned. Include RLS policy review in PR checklist. Use Supabase's local development stack for policy validation before deployment.

Contingency: If a breach is discovered post-deployment, immediately revoke all integration credentials, rotate vault secrets, notify affected organizations, and apply emergency RLS patches.

medium impact medium prob technical

JSONB columns for field_mappings and sync_schedule lack database-level schema enforcement; AI-generated or malformed JSON could silently corrupt integration configurations, causing export failures that are hard to diagnose.

Mitigation & Contingency

Mitigation: Define TypeScript/Dart model classes with strict deserialization and validation. Add database check constraints or triggers that validate JSONB structure at write time. Version the JSONB schema to enable forward-compatible migrations.

Contingency: Build a repair script that scans organization_integrations for invalid JSONB and resets corrupted records to a safe default state, alerting the admin of the affected organization.