critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

renderDeclarationDocument(templateId, driverContext) returns a non-empty UTF-8 encoded String with all template variables replaced by context values
Template variable syntax is {{VARIABLE_NAME}} (double curly braces, uppercase) — other text is preserved verbatim
If any required variable defined in the template is absent from driverContext, the method throws MissingTemplateVariableException listing all missing variable names before any substitution occurs
If templateId does not exist in the template repository, TemplateNotFoundException is thrown
The rendered document contains no unresolved {{...}} placeholders — a post-render validation pass verifies this and throws if any remain
driverContext must include at minimum: driver_name, assignment_date (formatted as DD.MM.YYYY), org_name, assignment_description — these are validated as non-empty before rendering
Template content is fetched from Supabase (declaration_templates table) and cached in memory for the lifetime of the service instance
The returned string is valid UTF-8 and does not contain null bytes or control characters (validate before return)
Variable substitution is XSS-safe — no HTML escaping is applied (the output is plain text for encryption, not HTML)
Rendering a template with 10 variables and a 5 KB template body completes in under 50 ms

Technical Requirements

frameworks
Flutter
flutter_test
apis
Supabase PostgREST REST API (declaration_templates table select)
data models
DeclarationTemplate (id, org_id, body_utf8, required_variables: List<String>, version, created_at)
DriverContext (driver_name, assignment_date, org_name, assignment_description, optional additional fields)
RenderedDeclaration (content: String, template_id, template_version, rendered_at)
performance requirements
Template fetch is cached — repeated calls for the same template_id within the service instance must not trigger a Supabase round-trip
String substitution must use a single-pass regex replace, not repeated replaceAll calls per variable
security requirements
Template content fetched from Supabase must be org-scoped via RLS (declaration_templates.org_id = auth.jwt().org_id)
driverContext values must be treated as untrusted input — strip null bytes and control characters (U+0000–U+001F except tab/newline) before substitution
Template IDs must be validated as non-empty UUIDs before the Supabase query to prevent injection

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement the substitution engine as a pure static method DeclarationTemplateRenderer.render(String templateBody, Map context) — no dependencies, fully unit-testable. Use RegExp(r'\{\{([A-Z_]+)\}\}', multiLine: true) to find all placeholders in one pass; collect missing variables, then substitute. Cache templates in a Map on the DeclarationTemplateRepository implementation using Riverpod's keepAlive provider or a simple in-memory map. The DriverContext should be a strongly-typed immutable data class (use freezed or hand-written copyWith) — not a raw Map — to prevent callers from passing arbitrary keys.

Convert DriverContext to Map only inside the renderer. Store template versions in the DeclarationTemplate record so the rendered document can reference which template version was used (important for legal audit trail in task-010).

Testing Requirements

Unit tests (flutter_test) with an in-memory mock template repository: happy path substitution with all variables present, missing one required variable (exception lists the missing name), missing multiple variables (exception lists all), templateId not found, post-render unresolved placeholder detection, empty driverContext field (non-empty validation), large template (10 KB body, 20 variables) within 50 ms. Integration test: fetch a real template from Supabase test project and render with a valid context, verify no {{...}} remain in output. Snapshot test: render a known template with a fixed context and assert the output string byte-for-byte.

Epic Risks (4)
high impact medium prob security

Org-scoped encryption key management is complex. If keys are not correctly isolated per organization, a breach in one org's key could expose another org's declarations. Additionally, key rotation is not specified but may be needed for compliance, and the current implementation may not support it.

Mitigation & Contingency

Mitigation: Use Supabase Vault or a dedicated secrets management approach for org-scoped key storage. Define the key derivation strategy (per-org master key) in a security design document reviewed before implementation begins. Include key isolation tests in the test suite.

Contingency: If a full per-org key management system cannot be safely implemented within the sprint, fall back to a single platform-level encryption key with strict RLS isolation as a temporary measure, flagging the key rotation gap as a security debt item with a defined resolution milestone.

medium impact medium prob integration

Push notification delivery to drivers depends on FCM token availability and device connectivity. If a driver has not granted notification permissions or has an expired FCM token, the declaration delivery notification will silently fail, leaving the coordinator unaware and the declaration unacknowledged.

Mitigation & Contingency

Mitigation: Implement delivery status tracking in declaration-notification-service. Fall back to in-app notification and SMS (if configured) when push delivery fails. Expose delivery failure status in the declaration status badge so coordinators can identify and manually follow up.

Contingency: If push delivery proves unreliable, implement a polling-based in-app notification fallback where drivers see pending declarations on next app open, ensuring the workflow can complete even without push notifications.

medium impact medium prob technical

The acknowledgement service is meant to validate that the driver has fully scrolled through the declaration before confirming. Implementing reliable scroll completion detection in Flutter across different screen sizes and font sizes is technically non-trivial and could be bypassed.

Mitigation & Contingency

Mitigation: Implement scroll position tracking using ScrollController with a threshold (e.g., 95% of content height reached) and record the validated state server-side before allowing acknowledgement submission. Document the approach in the legal sign-off checkpoint noted in the feature documentation.

Contingency: If reliable scroll detection cannot be implemented within the sprint, add a mandatory reading delay timer (e.g., estimated reading time based on word count) as an alternative validation mechanism, pending legal review of the approach.

medium impact low prob dependency

The driver assignment service must coordinate with the threshold-based expense approval workflow for fees above configured thresholds. If the expense approval workflow interface changes or is not yet stable, the integration point could break or produce incorrect routing behavior.

Mitigation & Contingency

Mitigation: Define a clear interface contract between driver-assignment-service and the expense approval workflow before implementation. Use dependency injection so the expense workflow client can be mocked in tests. Monitor the expense approval feature for interface changes.

Contingency: If the expense approval workflow interface is not stable, implement a direct database insert to the expense records table as a temporary bypass, with a flag indicating manual review is needed, until the stable interface is available.