high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

The collector queries only attachments belonging to activities within the provided report period and organisation scope — no cross-organisation data leakage
Signed URLs are generated with a configurable expiry duration (default 1 hour) using Supabase Storage's `createSignedUrl` API
Each attachment is validated for file availability before inclusion: HTTP HEAD request (or Supabase stat) confirms the object exists; unavailable files are excluded and logged with a warning
The returned manifest is a typed Dart list of `BufdirAttachmentManifestEntry` objects containing: activity_id, attachment_id, original_filename, signed_url, file_size_bytes, mime_type, and activity_date
Attachments for activities with no linked documents are not included — the manifest contains only entries with confirmed file presence
If zero attachments are found for the export scope, an empty manifest is returned without error — callers handle the empty case gracefully
The collector respects Supabase RLS: queries execute under the organisation-scoped service role context set by the Edge Function JWT, preventing row access violations
File availability check failures for individual attachments do not abort the entire collection — partial manifests with warnings are valid output
All signed URL generation calls are batched where the Supabase SDK allows, to avoid N+1 HTTP round-trips for large activity sets
Collection duration is logged as a metric (start time, end time, attachment count) for observability
Unit test verifies that an activity with three linked attachments produces three manifest entries with correct field mapping

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
Riverpod
apis
Supabase Storage API (createSignedUrl)
Supabase PostgreSQL (generated_files / attachments join query)
data models
activity
bufdir_export_audit_log
performance requirements
Signed URL batch generation must complete in under 3 seconds for up to 200 attachments
Database query for attachment metadata must use indexed columns (activity_id, organisation_id) with no sequential scans
File availability checks must run in parallel (Future.wait) with a concurrency cap of 10 to avoid socket exhaustion
security requirements
Signed URLs must never be logged or stored in the audit log — only attachment IDs and filenames are logged
Service role key used for Storage API calls must remain server-side in the Edge Function environment — never passed to the mobile client
Supabase RLS enforced: collector queries must scope to organisation_id from the JWT claims, validated before query execution
Signed URLs expire after 1 hour maximum to limit exposure window if the manifest is cached

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model the collection phase as a dedicated method `collectAttachmentManifest(String organisationId, String reportPeriodId)` on the `BufdirAttachmentBundler` class. Use a Supabase join query on the attachments table filtered by `organisation_id` and a subquery of activity IDs within the report period — avoid multiple round-trips. Batch signed URL creation using `supabase.storage.from(bucket).createSignedUrls(paths, expiresIn)` which accepts a list — this avoids N+1 calls. For availability validation, use `Future.wait` with a pool pattern (e.g.

`package:async`'s `Pool`) capped at 10 concurrent requests. Define `BufdirAttachmentManifestEntry` as an immutable Dart class with `copyWith`. Log warnings via a structured logger, not `print()`. Store the manifest as a `List` in a `BufdirExportContext` object passed to the next pipeline phase (task-009).

Ensure the method is `async` and returns `Future>` — never void.

Testing Requirements

Unit tests (flutter_test with Mockito or mocktail) must cover: (1) happy path — mock Supabase returning 3 attachments for 2 activities, assert manifest has 3 entries with correct field values; (2) unavailable file — one attachment returns 404 on availability check, assert it is excluded and a warning is emitted; (3) empty scope — no activities in period, assert empty manifest returned without exception; (4) RLS boundary — query executed with wrong org context returns empty result, assert no crash; (5) partial failure — 2 of 5 signed URL generations fail, assert 3 entries in manifest with 2 warnings logged. Integration tests against a Supabase test project should cover the full collection cycle with real Storage objects. Test coverage for BufdirAttachmentBundler collection phase must be at least 90%.

Component
Document Attachment Bundler
service medium
Epic Risks (3)
high impact medium prob technical

NHF contacts can belong to up to five local chapters simultaneously. If the deduplication logic in the activity query service incorrectly attributes cross-chapter activities, organisations will either under-report or over-report to Bufdir, which could trigger grant clawback or compliance investigations.

Mitigation & Contingency

Mitigation: Implement deduplication using the existing multi-chapter membership service as the source of truth for chapter affiliation. Write test fixtures covering all known multi-chapter edge cases and validate outputs against manually prepared reference exports from NHF.

Contingency: If deduplication cannot be made deterministic for complex hierarchies before release, gate the export behind an org-level feature flag and require NHF to validate a preview export against their manual Excel before enabling in production.

medium impact medium prob dependency

Server-side Dart libraries for Excel generation are less mature than equivalents in Node.js or Python. The chosen library may lack support for Bufdir-required formatting features (merged cells, data validation, specific date formats), requiring significant workaround effort or a library switch mid-implementation.

Mitigation & Contingency

Mitigation: Evaluate the top two Dart xlsx libraries (excel, spreadsheet_decoder) against a Bufdir template sample file before committing. Identify all required formatting features and verify library support in a spike.

Contingency: If no Dart library meets requirements, implement the Excel generation as a Supabase Edge Function in TypeScript using the well-supported ExcelJS library, exposing it to the Dart backend via an internal RPC call.

medium impact medium prob integration

The attachment bundler must retrieve documents from Supabase Storage that were uploaded by the document attachments feature. If storage paths, RLS policies, or signed URL expiry have not been standardised across features, the bundler may fail to retrieve attachments at export time.

Mitigation & Contingency

Mitigation: Audit the document attachments feature's storage schema and RLS policies before implementing the bundler. Agree on a stable internal service-account access pattern for cross-feature storage reads.

Contingency: If cross-feature storage access cannot be made reliable, implement the bundler to include only attachments that can be retrieved successfully and produce a manifest listing any attachments that could not be bundled, rather than failing the entire export.