high priority medium complexity backend pending backend specialist Tier 5

Acceptance Criteria

createIntegrationConfig returns a ConfigConflictError (not a database constraint error) when an active configuration already exists for the same orgId + integrationType combination
ConfigConflictError includes: conflictingConfigId, existingStatus, createdAt, and a user-friendly message in English (e.g., 'An active Xledger integration already exists for your organisation. Would you like to replace it?')
replaceIntegrationConfig(orgId, integrationType, newConfigData, newCredentials) atomically deactivates the existing config and creates the new one — no window where both are active simultaneously
replaceIntegrationConfig rolls back the entire operation if the new config creation fails after the old config was deactivated
upgradeIntegrationConfig(orgId, integrationType, updatedFields) updates fields on the existing active config without creating a new record — used for non-breaking configuration updates
Conflict detection runs at service layer BEFORE attempting any database write — no reliance on database constraint violation for normal flow
The UI wizard receives the ConfigConflictError with sufficient information to offer the user a 'Replace' or 'Cancel' choice without an additional API call
Uniqueness check is scoped to active configs only (status = 'active') — soft-deleted configs do not count toward uniqueness
The service correctly handles the edge case where a conflict check and a concurrent create arrive simultaneously — database-level unique constraint acts as final safety net, mapped to ConfigConflictError
All uniqueness enforcement logic is covered by unit tests including the race-condition database constraint fallback path

Technical Requirements

frameworks
Dart
Flutter
Supabase Edge Functions (Deno)
apis
Supabase PostgreSQL 15
Supabase Edge Functions REST API
data models
activity
annual_summary
performance requirements
Conflict check query completes within 200ms (indexed lookup on orgId + integrationType + status)
replaceIntegrationConfig full atomic operation completes within 3 seconds
upgradeIntegrationConfig completes within 1 second for field-only updates
security requirements
Uniqueness enforcement does not leak information about other organisations' configurations — conflict errors scoped strictly to the requesting org
replaceIntegrationConfig validates JWT org claim matches orgId before accessing any existing config
Race condition fallback (database constraint violation) mapped to ConfigConflictError before surfacing to client — raw Postgres constraint violation never reaches Flutter UI
All uniqueness operations logged in audit trail with user ID and timestamp

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Integration Task

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

Implementation Notes

Add uniqueness enforcement as a decorator or mixin layer on top of the IntegrationConfigService from task-010 — do not intermingle the uniqueness logic with the base CRUD methods. This keeps each concern separately testable. Implement an existsActiveConfig(orgId, integrationType): Promise private method that checks the database before any create attempt. For replaceIntegrationConfig, use a Postgres transaction with two operations: UPDATE existing set status='replaced', then INSERT new record.

Wrap in a database function/stored procedure for true atomicity. The user-friendly conflict message should include enough context for the UI wizard to render an actionable prompt — include existingConfigId so the wizard can call replaceIntegrationConfig directly. For the race condition safety net: catch Postgres unique_violation error code (23505) in the createIntegrationConfig catch block and convert it to ConfigConflictError with a generic message ('Configuration was created by another session — please refresh and try again'). The database-level unique index should be: CREATE UNIQUE INDEX ON integration_configs(org_id, integration_type) WHERE status = 'active' — this is a partial unique index that only enforces uniqueness on active records, which matches the business rule exactly.

Testing Requirements

Unit tests (Deno test): conflict detected when active config exists (mock repository returns existing record), conflict NOT detected when only soft-deleted configs exist (status != 'active'), ConfigConflictError contains all required fields for UI rendering, replaceIntegrationConfig deactivates old and creates new atomically (verify both operations called in correct order), rollback when new config creation fails mid-replace (verify old config re-activated), upgradeIntegrationConfig updates fields without creating new record, database constraint violation mapped to ConfigConflictError (not raw Postgres error), concurrent conflict race condition test using two simultaneous create calls. Test coverage target: 95%. All tests use mock repository and transaction mock — no live database required.

Component
Integration Configuration Service
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.