critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

AdapterRegistry registers all five adapters: XledgerAdapter, DynamicsAdapter, CornerstoneAdapter, ConsioAdapter, BufdirAdapter
Factory method getAdapter(integrationType: IntegrationType, orgId: string) returns the correct adapter instance loaded with the correct organisation credentials
Factory method throws AdapterNotFoundError with descriptive message when an unregistered integration type is requested
Adapter lifecycle management: adapters are instantiated lazily (on first use) and cached per-organisation per-type for the Edge Function invocation lifetime
Lookup API is accessible from both Edge Functions and Integration Config Service via a clean TypeScript/Deno module interface
Availability check isAdapterAvailable(integrationType) returns true/false based on whether a valid configuration exists for the requesting organisation
healthCheckAll() method runs healthCheck() on all registered adapters concurrently and returns a map of integrationType → HealthResult within 10 seconds
Graceful degradation: when an adapter healthCheck() fails, isAdapterAvailable() returns false and caller receives a degraded-service response rather than an unhandled exception
Registry correctly isolates adapter instances by organisation — org A's Xledger adapter instance cannot be returned for org B
All five adapters are correctly loaded and their capabilities are queryable via getAdapterCapabilities(integrationType)
Unit tests cover: correct adapter returned for each integration type, factory error for unknown type, org isolation, availability check logic, concurrent healthCheckAll() execution
Registry module exports are typed with TypeScript interfaces — no implicit any types

Technical Requirements

frameworks
Dart
Supabase Edge Functions (Deno)
apis
Supabase Edge Functions REST API
Supabase PostgreSQL 15
data models
activity
annual_summary
performance requirements
Adapter instance lookup (cache hit) must complete in under 10ms
healthCheckAll() runs all five health checks concurrently — must complete within 10 seconds total
Lazy instantiation ensures unused adapters incur zero initialization cost per Edge Function invocation
Registry module import must not add more than 50ms to Edge Function cold start
security requirements
Organisation context validated from JWT claims before any adapter instantiation or credential loading
Adapter instances isolated by organisation ID — the registry key must include both integrationType AND orgId
No cross-organisation credential bleed: registry must reject requests where JWT org does not match requested org
Registry does not persist credentials in memory between Edge Function invocations — credentials fetched fresh from vault on each instantiation

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Implement as a Deno TypeScript module (adapter_registry.ts) that can be imported by any Edge Function. Use a Map keyed by `${integrationType}:${orgId}` for the instance cache. The factory pattern should use a static registration map: `const ADAPTER_CONSTRUCTORS: Record = { xledger: XledgerAdapter, dynamics: DynamicsAdapter, ... }` — this makes adding future adapters a one-line change.

For lifecycle management, since Edge Functions are stateless (invocation-scoped), the 'cache' only persists for the duration of one function invocation — document this clearly. The healthCheckAll() method should use Promise.allSettled() (not Promise.all()) to ensure one failing health check does not block results from others. The availability check should query the integration_configurations table (via the repository layer from task-002) to verify an active config exists for the org + type combination before attempting instantiation. This prevents wasteful adapter construction when no config is set up.

Graceful degradation: wrap each healthCheck() call in try/catch and return a HealthResult with status: 'unavailable' and the error message on failure — never propagate raw exceptions from health checks.

Testing Requirements

Unit tests (Deno test): getAdapter returns correct adapter class for each of the five integration types, AdapterNotFoundError thrown for unknown type, org isolation verified (same type different org returns different instance with different credentials), isAdapterAvailable returns false when no config exists, healthCheckAll() resolves concurrently and maps all five results correctly, graceful degradation when one healthCheck throws (others still resolve). Mock all five adapter constructors and credential vault to keep tests fast and dependency-free. Test coverage target: 95% on registry logic. Run in CI on every commit since this is a critical routing layer.

Component
REST API Adapter Registry
service high
Epic Risks (3)
medium impact high prob technical

Each of the five external systems (Xledger, Dynamics, Cornerstone, Consio, Bufdir) has a different authentication flow, field schema, and error format. Forcing them into a uniform adapter interface may require compromises that result in leaky abstractions or make the adapter contract too complex to maintain.

Mitigation & Contingency

Mitigation: Design the IntegrationAdapter interface with a loose invoke() payload rather than a typed one, allowing each adapter to declare its own input/output schema. Use integration type metadata in the registry to document per-adapter quirks. Build Xledger first as the most documented API, then adapt the interface based on learnings.

Contingency: If the uniform interface cannot accommodate all five systems, split into two interface tiers: a simple polling/export adapter and a richer bidirectional adapter, with the registry declaring which tier each system implements.

medium impact high prob dependency

Development and testing of the Cornerstone and Consio adapters depends on NHF providing sandbox API access. If credentials or documentation are delayed, these adapters cannot be validated, blocking the epic's acceptance criteria.

Mitigation & Contingency

Mitigation: Implement Xledger and Dynamics adapters first (better-documented, sandbox available). Create a mock adapter for Cornerstone/Consio using recorded API responses for CI testing. Proactively request sandbox access from NHF at project kickoff.

Contingency: Ship the epic with Cornerstone/Consio adapters in a 'stub' state (connectivity test returns a simulated success, invoke() is not production-wired) and gate the NHF integration behind a feature flag until real API access is obtained.

medium impact medium prob scope

Real-world field mappings may include nested transformations, conditional logic, and data type coercions (e.g., Norwegian date formats, currency rounding rules) that the Field Mapping Resolver's initial design does not accommodate, requiring scope expansion mid-epic.

Mitigation & Contingency

Mitigation: Gather actual field mapping examples from Blindeforbundet (Xledger) and HLF (Dynamics) before designing the resolver. Identify the most complex transformation required and ensure the resolver design handles it. Limit Phase 1 to direct field renaming and format conversion only.

Contingency: If complex transformations are required, implement a simple expression evaluator (e.g., JSONata or a custom mini-DSL) as an extension point in the resolver, delivering basic mappings first and complex ones in a follow-up task.