high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

Adapter exposes a method (e.g., getChaptersForContact) that accepts an OrganisationContext parameter and appends a PostgREST `.eq('organisation_id', orgId)` filter clause to every query
All read queries on contact_chapters include both the RLS policy enforcement (server-side) and the explicit client-side filter (defence-in-depth)
Queries use the `organisation_id` index created in the migration step, confirmed by Supabase query plan (EXPLAIN shows index scan, not sequential scan)
A contact belonging to up to 5 chapters is returned correctly and completely without duplication
Passing a null or invalid OrganisationContext throws an ArgumentError before any network call is made
No cross-organisation data leaks: querying with org A context never returns rows belonging to org B (verified in integration test)
Filter clauses are composable — they can be combined with additional PostgREST filters (e.g., `.eq('status', 'active')`) without breaking query structure
Existing adapter methods (CRUD) are updated to require organisation context; no method accepts unbounded queries

Technical Requirements

frameworks
Flutter
Supabase Dart client (supabase_flutter)
apis
Supabase PostgREST REST API
Supabase Row Level Security policies
data models
ContactChapter
OrganisationContext
contact_chapters (Supabase table)
performance requirements
All filtered queries must hit the `idx_contact_chapters_organisation_id` index (no full table scans)
Single-contact chapter lookup must complete in under 300 ms on a standard Supabase free-tier instance
Bulk queries for up to 100 contacts must complete in under 2 seconds
security requirements
Client-side filter is defence-in-depth only — RLS policies on the Supabase side remain the primary enforcement layer
OrganisationContext must be derived from the authenticated user's JWT claims, never from client-supplied input without validation
No raw SQL string concatenation; use PostgREST filter builder API exclusively to prevent injection
Log (non-PII) a warning if a query is attempted without an organisation context in debug mode

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use the Supabase Dart client's fluent PostgREST builder (`.from('contact_chapters').select().eq('organisation_id', context.organisationId)`) — never build filter strings manually. Introduce an `OrganisationContext` value object (immutable Dart class with `organisationId` and optionally `userId`) to make the parameter contract explicit and type-safe. Pattern: create a private `_baseQuery(OrganisationContext ctx)` method that returns a pre-filtered QueryBuilder, then all public methods call `_baseQuery(ctx)` before adding further filters. This keeps filter logic in one place.

Avoid over-engineering: the context object does not need to hold the JWT itself, only the already-validated org ID. Guard the method with an `assert(ctx.isValid)` in debug mode. Confirm with the Supabase dashboard query analyser that the index is being used after inserting sufficient test data (the query planner only uses indexes when selectivity is high enough).

Testing Requirements

Unit tests (flutter_test): mock the Supabase PostgREST client and assert that every adapter method appends the correct `.eq('organisation_id', ...)` clause when given a valid OrganisationContext, and throws ArgumentError for null/invalid context. Integration tests: run against a local Supabase instance (or Supabase CLI) seeded with two organisations and shared contact IDs; verify that queries for org A never return rows from org B. Performance smoke test: insert 500 contact_chapter rows across two orgs and assert query latency is within threshold. Cover edge cases: contact with exactly 5 chapters, contact with 0 chapters, deactivated organisation.

Component
Supabase Contact Chapter Adapter
infrastructure medium
Epic Risks (2)
high impact medium prob security

Supabase RLS policies for a junction table that spans organisations are non-trivial. An incorrectly scoped policy could expose chapter affiliations from other organisations to coordinators, constituting a data breach.

Mitigation & Contingency

Mitigation: Draft RLS policies in a staging environment and run an explicit cross-organisation isolation test suite before merging. Use Supabase policy testing tools and peer review all policy definitions.

Contingency: If a policy error reaches review, roll back the migration and apply a corrective patch. Ensure no production data has been exposed by auditing Supabase logs for cross-organisation query results.

medium impact medium prob technical

The contact_chapters table migration may conflict with existing foreign key structures or require a backfill for contacts already assigned to a single chapter, causing migration failures in production.

Mitigation & Contingency

Mitigation: Write the migration as an additive, non-destructive operation. Backfill existing single-chapter assignments by deriving them from the existing contact records. Test the full migration on a production-sized dataset clone before release.

Contingency: Provide a rollback migration script that removes the new table without touching existing contact records. Coordinate with operations for a maintenance window if a re-run is needed.