critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

ActivityType is an immutable Dart class (all fields final) with a const constructor where possible
All fields match the activity_types database schema column names and types exactly (id, org_id, name, metadata as typed object, is_active, created_at, updated_at)
ActivityType exposes a fromJson(Map<String, dynamic>) factory constructor that correctly deserialises all fields including the nested metadata JSONB object
ActivityType exposes a toJson() method returning a Map<String, dynamic> suitable for Supabase upsert operations
copyWith() method is implemented for all fields to support immutable updates
ActivityTypeError is defined as a sealed class with at least three subtypes: ActivityTypeNotFound, ActivityTypeFetchFailure (wrapping the original exception), and ActivityTypePermissionDenied
Each error subtype carries sufficient context: ActivityTypeFetchFailure includes the original exception and stack trace; ActivityTypeNotFound includes the queried ID
A switch/when on ActivityTypeError exhaustively covers all subtypes without a default branch, confirmed by static analysis
Domain model has no dependencies on Supabase, Flutter, or any infrastructure package — pure Dart only

Technical Requirements

frameworks
Dart (pure — no Flutter dependency in this layer)
data models
ActivityType
ActivityTypeMetadata
performance requirements
fromJson must handle null metadata gracefully, returning a default ActivityTypeMetadata rather than throwing
security requirements
No sensitive data (PII, org secrets) stored on the ActivityType model beyond what is returned by the database
org_id field must always be present and non-nullable to enforce tenant isolation at the model level

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place the domain model in lib/domain/activity_type/ to keep it framework-agnostic. Prefer Dart sealed classes (Dart 3+) over abstract classes for ActivityTypeError — sealed gives exhaustive switch checking for free. Pattern: `sealed class ActivityTypeError {}` with `final class ActivityTypeNotFound extends ActivityTypeError { final String id; ... }`.

For the immutable model, consider using the 'equatable' or 'freezed' package if the rest of the codebase already uses one of them — check existing models before adding a new dependency. If freezed is in use, use @freezed for ActivityType and @freezed with unionValues for ActivityTypeError. fromJson should delegate metadata parsing to ActivityTypeMetadata.fromJson() which will be defined in task-006; add a TODO comment referencing that task if metadata model is not yet available.

Testing Requirements

Unit tests with flutter_test: (1) fromJson round-trip — serialise and deserialise a known JSON fixture and assert all fields are equal; (2) fromJson with null metadata field — assert a valid default ActivityTypeMetadata is returned; (3) fromJson with missing required fields — assert a descriptive FormatException is thrown; (4) copyWith — verify only the specified field changes; (5) sealed class exhaustiveness — write a switch on ActivityTypeError in a test helper and confirm the Dart analyser flags a missing case if a new subtype is added without updating the switch. No integration or e2e tests required for pure domain models.

Component
Activity Type Repository
data medium
Epic Risks (3)
high impact medium prob technical

The JSONB metadata column has no enforced schema at the database level. If the Dart model and the stored JSON diverge (e.g., a field is renamed or a new required flag is added without a migration), the metadata resolver will silently return null or throw at parse time, breaking conditional wizard logic for all organisations.

Mitigation & Contingency

Mitigation: Define a versioned Dart Freezed model for ActivityTypeMetadata and add a Supabase check constraint or trigger that validates the JSONB structure on write. Document the canonical metadata schema in a shared constants file and require schema review for any metadata field additions.

Contingency: Implement a lenient parse path in ActivityTypeMetadataResolver that returns safe defaults for missing fields and logs a structured warning to Supabase edge logs, allowing the app to degrade gracefully rather than crash.

high impact low prob security

If RLS policies on the activity_types table are misconfigured, a coordinator from one organisation could read or mutate activity types belonging to another organisation, violating data isolation guarantees required by all three client organisations.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase local emulator that explicitly assert cross-org isolation: a token scoped to org A must receive zero rows when querying org B activity types, and upsert attempts must return permission-denied errors.

Contingency: Apply an emergency RLS policy patch via Supabase dashboard without a code deploy. Audit all activity_type rows for cross-org contamination and restore from backup if any data leakage is confirmed.

medium impact medium prob integration

If the cache invalidation call in ActivityTypeService is not reliably triggered after an admin creates, edits, or archives an activity type, peer mentors on the same device will see stale data in the registration wizard until the next app restart, leading to confusion and potential misregistrations.

Mitigation & Contingency

Mitigation: Enforce a strict pattern: ActivityTypeService always calls cacheProvider.invalidate() inside the same try block as the successful Supabase mutation, before returning to the caller. Write a widget test that verifies the cache notifier emits an updated list after a service mutation.

Contingency: Add a background Supabase Realtime subscription on the activity_types table that triggers cache invalidation automatically, providing an independent safety net independent of the service call path.