high priority medium complexity integration pending integration specialist Tier 1

Acceptance Criteria

DynamicsPortalClient is a Dart class instantiated with a base URL and a credential provider (interface, not concrete Vault implementation) — enabling mock injection in tests.
Client fetches an OAuth token from Azure AD using client_credentials grant with credentials retrieved from the credential provider at call time (not cached beyond token expiry).
OAuth token is cached in memory with expiry tracking; a new token is fetched automatically when the cached token is within 60 seconds of expiry.
All HTTP requests include Authorization: Bearer <token> and a configurable x-correlation-id header for tracing.
Exponential backoff retry is applied for HTTP 429, 500, 502, 503, 504 responses: initial delay 1s, multiplier 2x, max 3 retries, max delay 16s. Non-retryable errors (400, 401, 403, 404) fail immediately.
Request and response details (method, URL, status, duration, correlation ID) are logged at debug level using the project's logger — no secrets or PII in log output.
Typed response models exist for all Dynamics endpoints used by this epic (e.g., DynamicsHlfSyncResponse, DynamicsCertificationRecord).
Client is designed to run inside a Supabase Edge Function (Deno-compatible) — uses dart:io HttpClient or http package available in the function runtime.
A DynamicsPortalClientException with code enum (authFailure, notFound, rateLimited, serverError, networkError) is thrown on non-success responses after retries.
Unit tests achieve 90%+ line coverage without making real network calls.

Technical Requirements

frameworks
Dart http package
Microsoft Dynamics 365 REST API
Azure AD OAuth2 (client_credentials)
apis
Microsoft Dynamics 365 REST API
Azure AD token endpoint
Supabase Vault (credential retrieval via Edge Function environment)
data models
certification
assignment
performance requirements
Token fetch must complete within 2 seconds; throw networkError if exceeded.
Total retry sequence (3 retries with backoff) must not exceed 45 seconds — add a total timeout guard.
HTTP connection pool should be reused across calls within the same Edge Function invocation.
security requirements
Azure AD client_id and client_secret must be loaded from Deno.env (injected from Supabase Vault) — never hardcoded or logged.
OAuth tokens must not be persisted to disk or database — in-memory cache only, scoped to the function invocation.
All API calls must use TLS; reject any base URL using plain HTTP at construction time.
Minimal required Dynamics permissions — read certification, write sync metadata only (per security context in CLAUDE.md).
Correlation ID must not include user identifiers — use a random UUID per request.
Dart credential provider interface must enforce that implementations cannot return null secrets — use non-nullable return types.

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep DynamicsPortalClient purely focused on HTTP mechanics — no business logic. Define a CredentialProvider abstract class with Future getCredentials() so tests inject a fake and production injects a VaultCredentialProvider that reads Deno.env. For token caching, store the access_token and an expiry DateTime in instance variables — check expiry before every call. Implement retry as a standalone retryWithBackoff(Future Function() fn, {required List retryableStatusCodes}) utility to keep it testable in isolation.

Use http.Client (injected via constructor) rather than calling http.get() directly — this is the standard approach for testable Dart HTTP code. Map Dynamics-specific error response bodies (JSON with error.code and error.message) to the DynamicsPortalClientException.code enum so callers get meaningful error types. Since this client runs inside a Supabase Edge Function (Deno), ensure all Dart code is compatible with the dart2js/Deno runtime — avoid dart:mirrors or platform-specific packages.

Testing Requirements

Write flutter_test (or pure Dart test) unit tests using a mock HTTP client (mocktail or http's MockClient) covering: (1) successful request returns typed model, (2) 429 response triggers retry with correct backoff delay, (3) 401 response throws DynamicsPortalClientException with authFailure code immediately (no retry), (4) 3 consecutive 503s exhaust retries and throw serverError, (5) token cache hit avoids a second token request within expiry window, (6) token cache miss (expired) triggers re-fetch before the next API call, (7) constructor rejects HTTP base URLs, (8) request log output contains no secret values. Integration test (manual / CI with test credentials): verify a real token can be obtained and a known Dynamics endpoint returns 200. Document how to run integration tests with environment variables set.

Component
Dynamics 365 Portal API Client
infrastructure medium
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for coordinator-scoped status queries may be difficult to express correctly, especially for peer mentors assigned to multiple coordinators or chapters, leading to data leakage or overly restrictive access blocking valid queries.

Mitigation & Contingency

Mitigation: Design RLS policies using security-definer RPCs rather than table-level policies for complex multi-coordinator scenarios. Write a comprehensive RLS test matrix covering all role and assignment permutations before marking complete.

Contingency: Fall back to application-level filtering in the repository layer with explicit coordinator_id parameter checks if RLS proves intractable, and document the trade-off for security review.

high impact medium prob dependency

The HLF Dynamics portal API contract may be undocumented or subject to change, causing the DynamicsPortalClient to break during development or production rollout.

Mitigation & Contingency

Mitigation: Obtain the full Dynamics portal API specification and credentials early in the sprint. Build the client behind a well-defined interface so the HLF-specific implementation can be swapped without affecting upstream services.

Contingency: If the Dynamics API is unavailable or unstable, stub the client with a feature-flag-guarded no-op implementation so all other epics can proceed to completion independently.

medium impact low prob technical

Supabase Edge Functions used as the nightly scheduler host may have cold-start latency or execution time limits that prevent reliable nightly certification checks on large mentor rosters.

Mitigation & Contingency

Mitigation: Benchmark Edge Function execution time against the expected roster size. Design the expiry check to process in paginated batches to stay within execution limits. Use pg_cron with a direct database function as an alternative trigger if Edge Functions prove unreliable.

Contingency: Migrate the scheduler trigger to pg_cron invoking a Postgres function directly, removing the Edge Function dependency entirely for the scheduling layer.