critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

RestApiAdapter is defined as an abstract Dart class (or interface) with five method signatures: authenticate(), testConnection(), exportPayload(), fetchRemoteSchema(), getCapabilities()
authenticate() returns Future<AuthResult> containing success flag and optional token/session data
testConnection() returns Future<ConnectionTestResult> containing isReachable bool, latencyMs int, and optional errorMessage
exportPayload(Map<String, dynamic> payload) returns Future<ExportResult> containing success flag, remoteId (assigned by external system), and optional errorMessage
fetchRemoteSchema() returns Future<RemoteSchema> containing a list of RemoteFieldDefinition objects describing the external system's expected fields
getCapabilities() returns List<IntegrationCapability> — synchronous, no async
AdapterRegistry is implemented with register(String typeKey, RestApiAdapter adapter) and lookup(String typeKey) → RestApiAdapter? methods
Registering a duplicate typeKey throws a DuplicateAdapterException
Looking up an unregistered typeKey returns null
AdapterRegistry.lookupOrThrow(String typeKey) throws UnregisteredAdapterException if not found
Unit tests verify interface contract: a MockAdapter implementing RestApiAdapter compiles and all methods are callable, registry register/lookup/duplicate/missing scenarios all behave as specified

Technical Requirements

frameworks
Flutter
Dart
Riverpod
data models
RestApiAdapter (abstract)
AdapterRegistry
AuthResult
ConnectionTestResult
ExportResult
RemoteSchema
RemoteFieldDefinition
IntegrationCapability (from Registry task-001)
performance requirements
Registry lookup must be O(1)
getCapabilities() must be synchronous and return in under 1ms
security requirements
AuthResult must not expose raw credentials — only tokens/session handles
Adapter implementations must not store credentials in instance state beyond the duration of a single operation

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Integration Task

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

Implementation Notes

Define RestApiAdapter as an abstract class rather than a Dart abstract interface (pre-Dart 3) or use `interface class` if targeting Dart 3+. Each method should have detailed dartdoc comments explaining expected behavior, error conditions, and what exceptions are permissible to throw. For AdapterRegistry, back it with a simple Map — no complex DI needed at this layer. Expose a global AdapterRegistry instance via a Riverpod Provider so adapters can be registered at app startup in main.dart or a dedicated registration module.

The open/closed principle is the key architectural goal: adding a sixth adapter (e.g., a future SAP integration) should require only creating a new class implementing RestApiAdapter and calling registry.register('sap', SapAdapter()) — no changes to existing code. Document this pattern explicitly in the class dartdoc.

Testing Requirements

Unit tests using flutter_test. Create a MockAdapter that implements RestApiAdapter with configurable return values. Tests: (1) MockAdapter satisfies interface contract — all five methods callable, (2) AdapterRegistry.register() succeeds for new key, (3) AdapterRegistry.register() throws DuplicateAdapterException for duplicate key, (4) AdapterRegistry.lookup() returns correct adapter for registered key, (5) AdapterRegistry.lookup() returns null for unregistered key, (6) AdapterRegistry.lookupOrThrow() throws UnregisteredAdapterException for unregistered key, (7) getCapabilities() on MockAdapter returns synchronously without await. All tests synchronous except where Future return values are verified with await.

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.