critical priority medium complexity backend pending backend specialist Tier 6

Acceptance Criteria

Every call to saveIntegrationConfig() and updateIntegrationConfig() invokes IntegrationConfigValidator.validate() before any database write is attempted
If validation returns errors, no database write occurs — the method returns a ValidationFailure result containing all errors
Validation errors are returned as a structured List<ConfigValidationError> where each error has a fieldPath, errorCode, and humanReadableMessage
If validation passes, the save proceeds and returns a SaveSuccess result containing the persisted config
The return type of save/update is a Dart sealed class / Result type (e.g., ConfigSaveResult) — never throws for validation failures
All three validation categories are enforced: (1) credential completeness, (2) field mapping coverage, (3) sync schedule validity
Partial saves are impossible: if any validation rule fails, the entire config is rejected atomically
The validator is injected into the Config Service as a dependency — not instantiated internally — enabling independent testing
Error messages are human-readable and suitable for display in the setup wizard UI
Validation runs synchronously where possible; async adapter capability checks are awaited before persistence begins

Technical Requirements

frameworks
Dart
BLoC
Riverpod
freezed (for sealed Result types)
apis
IntegrationConfigValidator.validate() interface
Supabase PostgREST (write path only after validation passes)
data models
IntegrationConfig
ConfigValidationError
ConfigSaveResult
FieldMapping
SyncSchedule
EncryptedCredential
performance requirements
Validation must complete within 2 seconds for all synchronous rules
Async adapter capability validation must complete within 3 seconds
Failed validation must not incur any Supabase round-trip cost
security requirements
Credential completeness validation must check that required credential fields are non-empty without logging their values
Validation errors must not expose credential values in error messages or field paths
The validator must not have write access to the database — it is a read-only query component

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Integration Task

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

Implementation Notes

Model the return type using a sealed class pattern: `sealed class ConfigSaveResult {}` with subclasses `ConfigSaveSuccess(IntegrationConfig config)` and `ConfigSaveFailure(List errors)`. This enables exhaustive switch matching in BLoC event handlers. The validator call should be `final validationResult = await validator.validate(config);` followed by an early return if errors are present. Never use try/catch to absorb validation errors — they are expected outcomes, not exceptions.

If the validator itself throws (e.g., network error fetching adapter capabilities), catch it and surface as a `ConfigSaveFailure` with a single error of errorCode='VALIDATION_UNAVAILABLE' rather than propagating the exception. For the BLoC layer, ConfigSaveFailure should map to a state that populates field-level error hints on the setup wizard form — ensure the ConfigValidationError.fieldPath values match the form field identifiers used in the wizard UI.

Testing Requirements

Unit tests covering the save flow integration: (1) valid config passes validation and is persisted — Supabase client insert/upsert is called exactly once, (2) config failing credential validation returns ValidationFailure with errorCode='CREDENTIAL_INCOMPLETE' and Supabase client is never called, (3) config failing field mapping returns ValidationFailure with errorCode='MAPPING_INCOMPLETE' listing missing fields, (4) config failing sync schedule returns ValidationFailure with errorCode='INVALID_SCHEDULE', (5) multiple validation errors are all returned together (not fail-fast), (6) validator throwing unexpectedly is wrapped and re-surfaced as a domain error not a raw exception, (7) update flow applies same validation as save flow. Use a mock IntegrationConfigValidator that returns configurable results. Verify the Supabase mock's insert was called 0 times on any validation failure.

Component
Integration Config Validator
infrastructure medium
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.