critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

Every query built by ContactRLSQueryBuilder includes an organization_id filter as the first predicate in the chain
The organization_id filter uses .eq() for single-organisation users and .in_() (or equivalent) for users with multiple organisation memberships
It is structurally impossible to call any downstream query method without supplying a valid, non-null orgId — the method signature enforces this at compile time (non-nullable parameter)
For the NHF multi-chapter case, passing a list of organisation IDs produces an .in_() filter that correctly scopes to all listed chapters
Passing an empty list of organisation IDs throws an ArgumentError before any Supabase call is made
Passing a null orgId is rejected at compile time (Dart null-safety)
The organization_id predicate is prepended before role-based and search predicates in all query chain constructions, verified by inspecting filter order in unit tests
flutter analyze reports no errors or warnings on the modified files

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgREST query builder (.eq, .in_)
data models
OrganisationContext
Contact
PeerMentor
performance requirements
For multi-chapter queries, use a single .in_() filter rather than multiple .or() calls to avoid query plan degradation on the Supabase side
Supabase RLS policies should be the last line of defence; this client-side scoping is an additional guardrail and must not be relied upon as the sole security mechanism
security requirements
organisation_id values must be sourced exclusively from the authenticated session/JWT, never from user-supplied input or URL parameters
A user who has been removed from an organisation must not retain a cached orgId that still passes through this filter — the OrganisationContext provider must be the authoritative source
Log a warning (not an error) in debug mode if the orgId list contains duplicates, and deduplicate before building the query

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Structure the query builder as a fluent builder pattern: ContactRLSQueryBuilder.forOrganisation(orgId).withRole(role).withSearch(query).build(). This makes the mandatory organisation scoping the entry point — callers cannot reach withRole() without going through forOrganisation() first. For multi-chapter support, overload forOrganisation() to accept either a single String or a List; internally normalise to List and select eq vs in_ based on list length. Do not expose the underlying Supabase query object until build() is called — this prevents callers from appending predicates outside the builder's control.

Testing Requirements

Unit tests: (1) single orgId produces .eq filter with correct column and value; (2) list of two orgIds produces .in_ filter; (3) empty list throws ArgumentError before Supabase is called; (4) organization_id filter appears first in the filter chain regardless of call order. Use a mock Supabase query builder that records filter call sequence. All tests in flutter_test. These tests will be consolidated with the broader unit test suite in task-004.

Component
Contact RLS Query Builder
infrastructure low
Epic Risks (2)
high impact medium prob security

Existing Supabase RLS policies for the contacts and peer_mentors tables may not align with the application-level UserRole model, causing ContactRLSQueryBuilder to construct filter expressions that are redundant, conflicting, or that allow over-fetching. In a multi-chapter context (NHF), this could expose contacts belonging to other chapters.

Mitigation & Contingency

Mitigation: Audit and document the existing RLS policies against the UserRole enum before writing a single line of query builder code. Write integration tests asserting cross-organization data isolation using separate test user tokens for each role.

Contingency: If RLS policies are misaligned at runtime, add an explicit application-level organization_id equality check in ContactRepository as a secondary guard while the database policies are corrected in a coordinated migration.

medium impact low prob integration

The Supabase schema for contacts and peer_mentors tables may differ from the expected typed models — missing columns, renamed fields, or type mismatches — causing deserialization failures that surface only at runtime during integration testing.

Mitigation & Contingency

Mitigation: Document expected schema fields upfront and validate against the live Supabase schema at sprint start. Use freezed and json_serializable for compile-time-safe deserialization with explicit required/optional field declarations.

Contingency: Introduce nullable fields with safe defaults for any schema mismatches discovered in testing; log deserialization errors to the monitoring service so schema drift is caught before production deployment.