critical priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

Guard class exposes a `validate(String? organisationId)` method that throws `OrgIsolationException` when organisationId is null or empty
Guard exposes a `assertQueryScoped(Map<String, dynamic> queryParams)` method that throws when the params map does not contain an `organisation_id` key
All summary generation entry points call the guard before any database interaction; this is enforced via unit tests that verify the call order
Supabase RLS policies are verified to be active on all relevant tables (activities, summaries, peer_mentors) — verified via a Supabase migration test
Guard does not suppress or catch its own exceptions — callers are responsible for handling `OrgIsolationException`
Guard is stateless and has no Supabase dependency — it operates purely on input validation
Any attempt to run a summary generation without a valid organisationId results in a logged error and process abort, never silent failure
Guard is documented with clear error messages that include the calling context for debuggability

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
apis
Supabase RLS policy enforcement (server-side)
data models
Organisation
performance requirements
Guard validation is synchronous and completes in microseconds — no I/O
security requirements
Supabase RLS must be enabled on all tables accessed during summary generation — the guard is a defence-in-depth layer, not the sole control
Never use the Supabase service role key in mobile client code — always use anon key with RLS
OrgIsolationException must not expose internal query structures in its message

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep this class intentionally simple — it is a pure validation utility, not a middleware interceptor. Implement as a `final class OrgIsolationGuard` with only static or instance methods, no state. Define `OrgIsolationException extends Exception` in the same file. The guard should be called at the top of every public method in repositories and services that touch multi-tenant data.

Consider adding a `runScoped(String orgId, Future Function() operation)` helper that calls `validate()` then executes the operation — this pattern makes call sites clean and guards impossible to forget. Document clearly that RLS is the authoritative enforcement layer and this guard is a developer-experience safeguard to catch misconfigured call sites during development.

Testing Requirements

Unit tests with flutter_test: test `validate()` with null, empty string, whitespace-only, and valid orgId inputs; test `assertQueryScoped()` with missing key, null value, and valid value. Verify that `OrgIsolationException` carries a meaningful message. Integration smoke test: attempt to call the SummaryGenerationService without an orgId and assert the process aborts with the correct exception type before any DB call is made. No UI tests required for this task.

Component
Organisation Data Isolation Guard
infrastructure low
Epic Risks (4)
high impact medium prob technical

Supabase pg_cron or Edge Function retries could trigger multiple concurrent generation runs for the same period and organisation, producing duplicate summaries and sending multiple push notifications to users — a serious UX regression.

Mitigation & Contingency

Mitigation: Implement a database-level run-lock using an INSERT … ON CONFLICT DO NOTHING pattern keyed on (organisation_id, period_type, period_start). Only the first successful insert proceeds; subsequent attempts read the existing lock and exit early. Test with concurrent invocations in a Deno test suite.

Contingency: If duplicate summaries are detected post-deployment, add a deduplication cleanup job that removes all but the most recent summary per (user_id, period_type, period_start) and sends a corrective push notification.

medium impact low prob integration

FCM and APNs have different payload structures and size limits. An oversized or malformed payload could cause silent notification drops on iOS or delivery failures on Android, meaning mentors never learn their summary is ready.

Mitigation & Contingency

Mitigation: Build the PushNotificationDispatcher with separate FCM and APNs payload constructors, enforce a 256-byte body limit on the preview text, and run integration tests against the Firebase Emulator and a test APNs sandbox.

Contingency: Fall back to a generic 'Your periodic summary is ready' message if personalised preview text construction fails, ensuring delivery even when the personalisation pipeline encounters an error.

medium impact high prob scope

Outlier thresholds that are too tight will flag most mentors as outliers (alert fatigue for coordinators), while thresholds that are too loose will miss genuinely underactive mentors — directly undermining HLF's follow-up goal.

Mitigation & Contingency

Mitigation: Implement thresholds as configurable per-organisation database settings rather than hardcoded constants. Provide sensible defaults (underactive < 2 sessions/period, overloaded > 20 sessions/period) and document the tuning process for coordinators in the admin portal.

Contingency: If coordinators report threshold miscalibration after launch, expose a threshold configuration UI in the coordinator admin screen and allow real-time threshold adjustment without requiring a code deployment.

low impact high prob scope

The app may not have 12 months of historical activity data for all organisations at launch, making year-over-year comparison impossible for most users and rendering the comparison widget empty, which could disappoint users expecting Wrapped-style insights.

Mitigation & Contingency

Mitigation: Design the generation service to gracefully handle missing prior-year data by setting the yoy_delta field to null rather than zero. The UI must treat null as 'no comparison available' with appropriate placeholder copy rather than showing a misleading 0% delta.

Contingency: If historical data import from legacy Excel/Word sources becomes feasible, add a one-time backfill Edge Function that populates prior-year activity records from imported spreadsheets. Until then, explicitly communicate the data-availability limitation in the first summary each user receives.