high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

`loadOrgBadgeConfig(orgId)` fetches badge definitions from Supabase on the first call and returns the cached result on subsequent calls within a 5-minute TTL
Cache TTL is enforced using wall-clock time (DateTime.now()), not request count; a call at T+6 minutes fetches fresh data regardless of intervening calls
Cache is per-orgId — loading config for org A does not affect the cache for org B
`createBadgeDefinition` calls `validateCriteria` before any Supabase write; if validation fails, no write is performed and a `BadgeCriteriaValidationException` is thrown with a list of violated rules
`updateBadgeDefinition` updates only the supplied fields (partial update); unsupplied fields are not overwritten to null
`deleteBadgeDefinition` succeeds only if no mentor has already earned the badge (foreign key constraint or pre-flight check); returns a descriptive error if badges have been awarded
After any successful create/update/delete, the in-memory cache for the affected orgId is invalidated so the next `loadOrgBadgeConfig` fetches fresh data
All mutating operations (create/update/delete) are restricted to callers with the org_admin role; non-admin callers receive a `BadgePermissionException`
`validateCriteria` is a standalone synchronous method that can be called without triggering any Supabase operation
The Riverpod provider for BadgeConfigurationService is exported and accessible project-wide as `badgeConfigurationServiceProvider`

Technical Requirements

frameworks
Flutter
Riverpod (Provider)
Dart
apis
Supabase PostgREST — badge_definitions table (select, insert, update, delete)
Supabase RLS for org_admin role enforcement
data models
BadgeDefinition
BadgeCriteria
OrgBadgeConfig
performance requirements
Cache hit on loadOrgBadgeConfig must return in < 5 ms (no network)
Cache miss (Supabase fetch) must complete in < 400 ms under normal conditions
CRUD operations must complete in < 600 ms excluding network latency
security requirements
Supabase RLS policies must prevent non-admin roles from inserting, updating, or deleting badge_definitions rows
orgId parameter must be validated as a non-empty UUID before any Supabase call to prevent injection or data leakage
Deletion pre-flight check must be performed inside a Supabase transaction or RPC to prevent TOCTOU race conditions

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement caching with a simple `Map` where `_CacheEntry` holds the data and a `DateTime expiresAt`. Use a private `_isCacheValid(orgId)` helper. Do not use an external caching package — the in-memory map is sufficient and avoids additional dependencies. For partial updates, use Dart's named parameters with nullable types and build the Supabase update map dynamically, only including keys where the value is non-null.

For the deletion safety check, prefer a Supabase RPC function (e.g. `safe_delete_badge_definition`) that checks earned badges and deletes atomically, rather than two separate client calls. Keep `validateCriteria` as a pure synchronous function that takes a `BadgeCriteria` object and returns a `List` of error messages (empty = valid) — this makes it composable in the UI for real-time feedback before the user submits.

Testing Requirements

Unit tests: test `validateCriteria` with valid criteria, missing required fields, non-integer thresholds, zero threshold, negative threshold — each should produce the correct exception or pass. Test cache TTL: mock DateTime and verify that a call at T+5min+1s bypasses cache. Integration tests (mocked Supabase): verify create triggers validate then insert; verify update only sends changed fields; verify delete fails gracefully when earned badges exist; verify cache invalidation after mutating operations. Test role enforcement by providing a non-admin auth context and asserting BadgePermissionException is thrown for all mutating methods.

Component
Badge Configuration Service
service medium
Epic Risks (3)
high impact medium prob technical

peer-mentor-stats-aggregator must compute streaks and threshold counts across potentially hundreds of activity records per peer mentor. Naive queries (full table scans or N+1 patterns) will cause slow badge evaluation, especially when triggered on every activity save for all active peer mentors.

Mitigation & Contingency

Mitigation: Design aggregation queries using Supabase RPCs with window functions or materialised views from the start. Add database indexes on (peer_mentor_id, activity_date, activity_type) before writing any service code. Profile all aggregation queries against a dataset of 500+ activities during development.

Contingency: If query performance is insufficient at launch, implement incremental stat caching: maintain a peer_mentor_stats snapshot table updated on each activity insert via a database trigger, so the aggregator reads from pre-computed values rather than scanning raw activity rows.

medium impact low prob technical

badge-award-service must be idempotent, but if two concurrent edge function invocations evaluate the same peer mentor simultaneously (e.g., from a rapid double-save), both could pass the uniqueness check before either commits, resulting in duplicate badge records.

Mitigation & Contingency

Mitigation: Rely on the database-level uniqueness constraint (peer_mentor_id, badge_definition_id) as the final guard. In the service layer, use an upsert with ON CONFLICT DO NOTHING and return the existing record. Add a Postgres advisory lock or serialisable transaction for the award sequence during the edge function integration epic.

Contingency: If duplicate records are discovered in production, run a deduplication migration to remove extras (keeping earliest earned_at) and add a unique index if not already present. Alert engineering via Supabase database webhook on constraint violations.

medium impact medium prob scope

The badge-configuration-service must validate org admin-supplied criteria JSON on save, but the full range of valid criteria types (threshold, streak, training-completion, tier-based) may not be fully enumerated during development, leading to either over-permissive or over-restrictive validation that frustrates admins.

Mitigation & Contingency

Mitigation: Define a versioned Dart sealed class hierarchy for CriteriaType before writing the validation logic. Review the hierarchy with product against all known badge types across NHF, Blindeforbundet, and HLF before implementation. Build the validator against the sealed class so new criteria types require an explicit code addition.

Contingency: If admins encounter validation rejections for legitimate criteria, expose a 'criteria_raw' escape hatch (JSON passthrough, admin-only) with a product warning, and schedule a sprint to formalise the new criteria type properly.