critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Migration script executes successfully on a clean Supabase instance without errors
badge_definitions table has columns: id (uuid PK), organisation_id (uuid FK), name (text NOT NULL), description (text), icon_ref (text), criteria (jsonb NOT NULL DEFAULT '{}'), is_active (boolean DEFAULT true), created_at (timestamptz), updated_at (timestamptz)
earned_badges table has columns: id (uuid PK), organisation_id (uuid FK), user_id (uuid FK), badge_definition_id (uuid FK), awarded_at (timestamptz), awarded_by (uuid FK nullable), status (text CHECK IN ('active','revoked')), revoked_at (timestamptz nullable), revoked_by (uuid FK nullable)
recognition_tiers table has columns: id (uuid PK), organisation_id (uuid FK), name (text NOT NULL), threshold (integer NOT NULL), icon_ref (text), colour_token (text), created_at (timestamptz)
tier_assignments table has columns: id (uuid PK), organisation_id (uuid FK), user_id (uuid FK), tier_id (uuid FK), assigned_at (timestamptz), assigned_by (uuid FK nullable)
UNIQUE constraint on earned_badges(organisation_id, user_id, badge_definition_id) prevents duplicate active awards
UNIQUE constraint on tier_assignments(organisation_id, user_id) ensures one active tier per volunteer per organisation
CHECK constraint on earned_badges.status enforces values ('active', 'revoked') only
All FK references to organisations, users, and cross-table references are correctly defined with ON DELETE behaviour documented
criteria JSONB column in badge_definitions accepts and stores threshold rules such as {"type": "assignment_count", "threshold": 3} and {"type": "assignment_count", "threshold": 15} without schema changes
Migration is idempotent — running it twice does not produce errors
Migration script includes rollback (DOWN migration) that cleanly removes all four tables
Indexes are created on organisation_id columns for all tables and on user_id for earned_badges and tier_assignments for query performance

Technical Requirements

frameworks
Supabase
apis
Supabase Migrations CLI
PostgreSQL DDL
data models
BadgeDefinition
EarnedBadge
RecognitionTier
TierAssignment
performance requirements
Composite index on (organisation_id, user_id) for earned_badges to support fast per-volunteer lookups
Index on badge_definitions.organisation_id for filtered definition fetches
Index on recognition_tiers(organisation_id, threshold) to support ordered tier resolution
All FK columns indexed to prevent full-table scans on joins
security requirements
organisation_id present on every table as the RLS anchor column
No user PII stored in badge tables — only UUID references to user records
criteria JSONB column must not store personally identifiable threshold data
awarded_by and assigned_by audit columns nullable to support system-generated awards
Soft-delete pattern used for badge revocations (status column) rather than hard deletes to preserve audit trail

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase CLI migration workflow (`supabase migration new badge_schema`). Write the UP migration as a single transaction so it rolls back atomically on failure. The criteria JSONB schema should be documented via a JSON Schema comment in the migration file — Supabase does not enforce JSONB structure natively, so document the expected shape: `{"type": "assignment_count", "threshold": , "honorar_level": }`. For the UNIQUE constraint on earned_badges, consider a partial unique index `WHERE status = 'active'` to allow revoked-then-re-awarded scenarios.

The tier_assignments UNIQUE constraint should similarly be partial if re-assignment is a valid workflow. Coordinate with the RLS task (task-002) — schema must be finalised before policies are written. Avoid using SERIAL/SEQUENCE for IDs; use `gen_random_uuid()` as the default for all PK columns to align with Supabase conventions.

Testing Requirements

Write SQL integration tests using pgTAP or Supabase's built-in test runner. Tests must verify: (1) successful INSERT into all four tables with valid data, (2) UNIQUE constraint violation when attempting to award the same badge twice to the same user in the same organisation, (3) CHECK constraint rejection for invalid status values, (4) FK constraint enforcement (inserting earned_badge with non-existent badge_definition_id must fail), (5) criteria JSONB column stores and retrieves complex threshold objects correctly, (6) DOWN migration cleanly removes all tables and constraints. Run migrations against a local Supabase instance as part of CI. At least one test per constraint type.

Component
Badge Repository
data low
Epic Risks (2)
high impact medium prob scope

Badge criteria are stored as structured JSON in badge_definitions. If the JSON schema for criteria (threshold counts, streak lengths, training completion flags) is not well-defined upfront, the evaluation service will be built against a moving target, requiring costly migrations and refactors.

Mitigation & Contingency

Mitigation: Define and document the criteria JSON schema in a shared type file before any repository code is written. Review the schema with all three organisations' badge requirements — especially Blindeforbundet's honorar thresholds — and version the JSON schema using a 'criteria_version' field from day one.

Contingency: If the criteria schema must change after services are built, write a Supabase migration to backfill existing rows and add a migration version column. Keep the evaluation service criteria parser isolated behind an interface so only one function needs updating.

medium impact medium prob dependency

Badge icon assets may not yet exist or may fail WCAG 2.2 AA contrast validation (minimum 3:1 for graphical objects) when rendered over design-token backgrounds. Missing or non-compliant icons could block UI epic delivery for Blindeforbundet, for whom screen reader and visual accessibility is non-negotiable.

Mitigation & Contingency

Mitigation: During this epic, implement the contrast-ratio validator in badge-icon-asset-manager and run it as a Flutter test against all candidate icon assets early. Coordinate with the design team to provide WCAG-compliant SVG icons in both locked and unlocked variants before the UI epic begins.

Contingency: If assets are late or fail contrast checks, ship placeholder icons that are guaranteed compliant (solid design-token colour fills with text labels) and swap in final assets post-QA without requiring a code change.