critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

BufdirExportAuditService implements ExportAuditService and receives ExportAuditRepository via constructor injection
createRecord generates a UUID v4 auditId, sets status=initiated, sets initiatedAt=DateTime.now().toUtc(), persists via repository, and returns the full ExportAuditRecord
updateStatus fetches the current record, validates the state machine transition via ExportAuditRecord.advanceStatus, persists the updated record, and returns the updated record
attachFileRef fetches the current record, validates status==completed (throws if not), attaches ExportFileRef, updates lastUpdatedAt, persists, and returns updated record
No delete operation is exposed on ExportAuditService or ExportAuditRepository — repository interface has no remove/delete methods
All persistence operations use the repository's upsert-by-auditId pattern — INSERT on create, UPDATE on status change — never DELETE
InvalidStatusTransitionException from the domain model propagates to the caller without wrapping
Service is stateless — all state lives in the repository/database
Concurrent calls to updateStatus with the same auditId are safe — repository uses optimistic locking or Supabase row-level update with returning clause
All DateTime values stored and returned in UTC
Abstract ExportAuditRepository interface defines: createAuditRecord, updateAuditRecord, getAuditRecord(String auditId) — no delete method

Technical Requirements

frameworks
Dart
Supabase (infrastructure layer via repository pattern)
Riverpod (DI wiring)
apis
Supabase PostgREST API (bufdir_export_audits table)
data models
ExportAuditRecord
ExportStatus
ExportFileRef
CreateAuditRecordParams
performance requirements
createRecord round-trip must complete within 1 second under normal network conditions
updateStatus must use targeted column updates (not full record upsert) to minimise Supabase write amplification
security requirements
Supabase Row Level Security (RLS) must restrict audit record reads to: the record's orgId admin users and the record's userId owner
RLS must prevent any client from deleting rows in bufdir_export_audits — enforced at database level, not just service level
userId stored in audit record must match the authenticated Supabase session user ID at service entry point
auditId generated as UUID v4 — never sequential integer — to prevent enumeration

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place service under lib/features/bufdir_export/infrastructure/audit/bufdir_export_audit_service.dart and repository interface under lib/features/bufdir_export/domain/audit/export_audit_repository.dart. Supabase repository implementation goes under lib/features/bufdir_export/infrastructure/audit/supabase_export_audit_repository.dart. Database table design: bufdir_export_audits with columns (audit_id UUID PK, org_id TEXT NOT NULL, user_id UUID NOT NULL, period_start TIMESTAMPTZ, period_end TIMESTAMPTZ, status TEXT CHECK(status IN ('initiated','in_progress','completed','failed')), initiated_at TIMESTAMPTZ NOT NULL, last_updated_at TIMESTAMPTZ NOT NULL, file_storage_key TEXT, file_name TEXT, file_size_bytes BIGINT, file_checksum TEXT, file_generated_at TIMESTAMPTZ). Use Supabase .update().eq('audit_id', id).select().single() for targeted updates — avoid re-inserting the whole row.

For optimistic concurrency on updateStatus, include .eq('status', currentStatus.toDbString()) in the update filter and throw a ConcurrentModificationException if zero rows affected. Register BufdirExportAuditService as a Riverpod Provider scoped to the feature.

Testing Requirements

Unit tests with mock ExportAuditRepository: test createRecord produces correct initial state, test updateStatus for all valid transitions (initiated→inProgress, inProgress→completed, inProgress→failed), test updateStatus throws InvalidStatusTransitionException for invalid transitions (e.g., completed→initiated). Test attachFileRef succeeds when status=completed, throws when status=inProgress or initiated. Test that no delete method exists on the repository interface (compile-time enforcement). Integration test against Supabase test instance: verify RLS blocks cross-org audit record reads, verify no DELETE privilege on table, verify UTC timestamp storage.

Target 90%+ branch coverage on service implementation.

Component
Export Audit Log Service
service medium
Epic Risks (2)
medium impact medium prob scope

Bufdir's column schema may have per-field business rules (conditional required fields, cross-field validation, organisation-specific category taxonomies) that cannot be expressed in a simple key-value mapping configuration. If the configuration model is too simple, supporting NHF's specific requirements will require hardcoded organisation logic, undermining the configuration-driven design.

Mitigation & Contingency

Mitigation: Design the column configuration schema as a full JSON document supporting field-level transformation rules, conditional expressions, and org-specific value enumerations. Validate the design against a real NHF Bufdir Excel template before implementation begins.

Contingency: If the configuration model cannot express all required rules, implement a thin transformation plugin interface where org-specific logic can be added as a named Dart class registered against the organisation ID, with the JSON config covering only the common cases.

high impact medium prob technical

For large organisations like NHF with potentially tens of thousands of activity records, the full export pipeline (query + map + generate + bundle + upload) may exceed Supabase Edge Function execution time limits (typically 150s), causing silent timeouts that leave audit records in a pending state indefinitely.

Mitigation & Contingency

Mitigation: Implement the orchestrator as a background Dart isolate with progress streaming rather than a synchronous Edge Function call. Use chunked processing for the query and mapping phases to reduce peak memory usage. Profile against realistic NHF data volumes in a staging environment.

Contingency: If processing time cannot be reduced below the timeout threshold, implement an asynchronous job model where the export is queued, processed in the background, and the user is notified via push notification when the download is ready — treating it as an eventual rather than synchronous operation.