critical priority medium complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

A new Supabase Edge Function directory exists at supabase/functions/certification-expiry-cron/index.ts
The function entry point exports a default Deno.serve handler that returns a 200 OK response with a JSON body when invoked manually via supabase functions invoke
The function is registered as a cron-triggered function in supabase/config.toml with a schedule of '0 2 * * *' (02:00 UTC daily)
A Supabase client is initialised inside the handler using createClient from @supabase/supabase-js with SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY sourced from Deno.env
SUPABASE_SERVICE_ROLE_KEY is never hardcoded — it is read exclusively from environment variables
The function module structure separates concerns: index.ts (entry/handler), db.ts (query logic placeholder), services.ts (service call placeholder), idempotency.ts (idempotency logic placeholder)
supabase functions deploy certification-expiry-cron executes without errors
Manual invocation via supabase functions invoke certification-expiry-cron --no-verify-jwt returns HTTP 200 with body { status: 'ok', message: 'scaffold ready' }
Function logs appear in supabase functions logs certification-expiry-cron within 30 seconds of invocation
A README.md in the function directory documents the cron schedule, required environment variables, and manual invocation command

Technical Requirements

frameworks
Supabase Edge Functions (Deno runtime)
Deno standard library
apis
Supabase Management API (for deployment)
supabase-js v2 createClient
data models
certification
performance requirements
Function cold-start must complete within 3 seconds
Entry point must be structured to allow async query and service logic without blocking the event loop
security requirements
Service role key read only from Deno.env — never hardcoded or committed to source control
Function input validated before any database operation — reject unexpected HTTP methods
Function scoped by organisation context derived from JWT claims or internal logic — no cross-org data access
Secrets stored in Supabase project secrets vault (supabase secrets set) and referenced in config.toml

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the Supabase CLI scaffold command (supabase functions new certification-expiry-cron) as a starting point, then extend it. Register the cron trigger in supabase/config.toml under [functions.certification-expiry-cron] with schedule = '0 2 * * *'. Import createClient from https://esm.sh/@supabase/supabase-js@2 — pin to a specific version to prevent unexpected breakage. Structure the handler to be an async function and use top-level await only for initialisation that must complete before serving.

Keep index.ts thin — it should only parse the request, call an orchestrateExpiryCheck() function (to be implemented in later tasks), and return the response. This makes unit-testing the business logic possible without spinning up the full Deno serve runtime. Document environment variable names exactly as they will be set in CI/CD secrets to avoid deployment mismatches.

Testing Requirements

Verify the scaffold with a manual invocation test: run supabase start locally, deploy the function with supabase functions deploy, invoke with supabase functions invoke certification-expiry-cron --no-verify-jwt, and assert the response body equals { status: 'ok', message: 'scaffold ready' }. Confirm the Supabase client initialises without throwing by adding a simple health-check query (e.g. select 1) inside the handler and asserting it does not return an error. Write a Deno unit test at supabase/functions/certification-expiry-cron/index_test.ts that mocks Deno.env and asserts the handler returns a Response with status 200.

This test should be runnable with deno test --allow-env.

Component
Certification Expiry Nightly Cron Job
infrastructure medium
Epic Risks (2)
medium impact low prob technical

Supabase Edge Functions can have cold-start latency that causes the nightly cron to time out when processing large cohorts of expiring certifications, resulting in partial reminder dispatches.

Mitigation & Contingency

Mitigation: Batch the cron processing in chunks of 50 mentors per iteration. Use pagination with a cursor to resume processing if the function is re-invoked. Keep total invocation time well under the Edge Function timeout limit.

Contingency: If timeouts occur in production, split the cron into two separate functions: one for reminders and one for auto-pauses, each with its own schedule offset to reduce peak load.

low impact medium prob technical

Certification BLoC covers three distinct workflows (view, renew, enrol) which may lead to an overly complex state machine that is hard to test and maintain, particularly when error states from multiple concurrent operations need to be differentiated in the UI.

Mitigation & Contingency

Mitigation: Use separate sealed state classes per workflow (CertificationViewState, RenewalState, EnrolmentState) composed into a single BLoC state wrapper. Follow the existing BLoC patterns established in the codebase for consistency.

Contingency: If the BLoC grows too complex, split into two BLoCs: CertificationBLoC (view/load) and CertificationActionBLoC (mutations), connected via a shared stream.