high priority low complexity database pending database specialist Tier 0

Acceptance Criteria

vipps_org_cost_config table exists in Supabase with all specified columns: org_id, subscription_active, monthly_cost_nok, billing_contact_user_id, cost_share_model, created_at, updated_at
org_id is a foreign key referencing the organizations table with ON DELETE CASCADE
billing_contact_user_id is a foreign key referencing auth.users with ON DELETE SET NULL
cost_share_model column has a CHECK constraint accepting only 'equal_split', 'proportional', and 'fixed' values
monthly_cost_nok is NUMERIC(10,2) with a CHECK constraint ensuring value >= 0
subscription_active defaults to false on row creation
created_at and updated_at are TIMESTAMPTZ with created_at defaulting to NOW()
An update trigger automatically sets updated_at to NOW() on every UPDATE
RLS is enabled on the table with a policy allowing SELECT for authenticated users within the same org
RLS UPDATE and INSERT policies restrict writes to users with the 'org_admin' role for the matching org_id
RLS DELETE policy restricts deletion to org admins only
Migration file is versioned and idempotent (safe to run multiple times without error)
A seed or documentation file lists the four partner organizations and their agreed cost_share_model values
All constraint and RLS policy names follow the project naming convention (e.g., vipps_org_cost_config_cost_share_model_check)

Technical Requirements

frameworks
Supabase
apis
Supabase Migrations API
Supabase RLS Policy API
data models
Organization
VippsOrgCostConfig
auth.users
performance requirements
Table should have an index on org_id for O(1) lookup by organization
Index on billing_contact_user_id for reverse lookups
Migration must complete in under 5 seconds on an empty database
security requirements
RLS must be enabled before the table is accessible in production
No public SELECT policy — only authenticated org members may read their own org config
billing_contact_user_id must never be exposed via a public-facing API without auth check
cost_share_model CHECK constraint enforced at database level as a hard guardrail
Migration must not include any hardcoded secrets or API keys

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase CLI migrations (supabase migration new vipps_org_cost_config). Define the cost_share_model CHECK constraint inline on the column definition rather than as a separate ALTER TABLE statement for readability. Create the update trigger using a reusable moddatetime() or a custom PL/pgSQL function if moddatetime is not available. For RLS, use (auth.uid() IN (SELECT user_id FROM org_members WHERE org_id = vipps_org_cost_config.org_id AND role = 'org_admin')) for UPDATE/INSERT policies.

Document the three cost-share model semantics clearly in a SQL comment block at the top of the migration: equal_split = total cost divided equally among active orgs, proportional = based on active user count, fixed = each org pays a fixed agreed amount regardless of usage. Coordinate with the team to confirm Vipps monthly cost range (350–750 NOK/month) matches the numeric precision chosen.

Testing Requirements

Write SQL-level tests using pgTAP or Supabase's built-in test runner to verify: (1) INSERT with invalid cost_share_model value is rejected by CHECK constraint, (2) INSERT with negative monthly_cost_nok is rejected, (3) an authenticated user from a different org cannot SELECT or UPDATE the row, (4) an org_admin can INSERT, UPDATE, and SELECT their own org's config, (5) updated_at is automatically updated on any UPDATE, (6) ON DELETE CASCADE removes the config row when the parent organization is deleted. Run migration in a clean test Supabase project to confirm idempotency.

Component
Vipps Organization Cost Configuration
infrastructure low
Epic Risks (3)
high impact medium prob technical

Flutter Secure Storage behavior differs between iOS Keychain and Android Keystore — key accessibility attributes (kSecAttrAccessibleWhenUnlocked vs. WhenUnlockedThisDeviceOnly) may cause tokens to become inaccessible after device restart or OS upgrade, breaking session restoration for returning users.

Mitigation & Contingency

Mitigation: Define explicit Keychain accessibility attributes during implementation and write integration tests on both platforms. Follow flutter_secure_storage documentation for cross-platform accessibility configuration.

Contingency: Implement a recovery flow that detects secure storage read failures and falls back to full re-authentication rather than crashing. Add a migration utility to re-write tokens with corrected attributes if a misconfiguration is discovered post-release.

high impact medium prob security

Personnummer is a legally sensitive national identifier under Norwegian GDPR implementation. If encryption-at-rest or data minimization requirements are not met before launch, the feature could be blocked by legal/compliance review from any of the four partner organizations.

Mitigation & Contingency

Mitigation: Ensure personnummer is only persisted after explicit user consent via the personnummer confirmation widget. Use Supabase column-level encryption for the personnummer field. Document the data processing basis and retention policy before the first TestFlight release.

Contingency: If legal review blocks the personnummer write-back, implement the feature as opt-in only with a deferred sync model, allowing BankID/Vipps login to proceed without storing the personnummer until compliance is confirmed.

medium impact high prob dependency

If the VippsOrgCostConfig data is not populated in Supabase for all four partner organizations before the feature ships, users from unconfigured organizations will see no Vipps login option and may report it as broken, creating confusion and support load.

Mitigation & Contingency

Mitigation: Create a seed migration script for Vipps org configuration and include it in the deployment checklist. Implement a clear admin UI warning when an organization is missing Vipps configuration.

Contingency: Add a feature flag in VippsOrgCostConfig so individual organizations can be enabled/disabled without a code deploy, allowing rapid remediation.