critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

DictationScopeGuard is registered as a Riverpod provider and is accessible throughout the app via ref.watch/ref.read
When ActiveSessionState is inactive/null, ScopeGuardResult.allowed is emitted on the stream
When ActiveSessionState transitions to active, ScopeGuardResult.blocked(reason: 'active_session') is emitted immediately
If a peer mentoring session starts while dictation is in progress, the stream emits a blocked result within 100ms of the state change
ScopeGuardResult sealed class exposes at minimum: allowed, blocked(String reason), and the current session context
The guard does not depend on platform-specific APIs — it only reads Riverpod state
Disposing the provider cancels all stream subscriptions without memory leaks
ScopeGuardResult.reason accurately identifies the cause (e.g., 'active_session', 'session_starting') for UI messaging

Technical Requirements

frameworks
Flutter
Riverpod
data models
ActiveSessionState
ScopeGuardResult
performance requirements
State change propagation to stream must occur within 100ms
No synchronous blocking — all checks are reactive/async via stream
security requirements
Dictation must never proceed during a live peer mentoring session to protect session confidentiality per Norwegian privacy norms (GDPR-adjacent)
Guard logic must be server-authoritative-ready: local state is used for UX, but session truth must not be bypassable client-side

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model ScopeGuardResult as a sealed class with two subclasses: ScopeGuardAllowed and ScopeGuardBlocked({required String reason}). Use Riverpod's StreamProvider or a StateNotifier that exposes a Stream internally. React to the ActiveSessionState provider using ref.listen inside a custom provider; emit new ScopeGuardResult values on each state change. Avoid polling — rely entirely on Riverpod reactivity.

The 'mid-dictation blocking' scenario is the most critical edge case: ensure the stream emits a blocked event even if dictation has already started, so the UI layer or DictationController can call stopListening() reactively. Do not embed any stopListening() logic inside the guard itself — it is the consumer's responsibility to react.

Testing Requirements

Unit tests must cover all state transition scenarios using mocked Riverpod providers (see task-013). Integration smoke test should verify that a real Riverpod container with a mock ActiveSessionState provider correctly drives stream output. No flutter_test widget tests are required for this task since it is a pure business-logic provider. Code coverage for this class should reach 100% of branching logic.

Component
Dictation Scope Guard
service low
Epic Risks (3)
high impact medium prob technical

iOS 15 on-device speech recognition has a 1-minute session limit and requires network fallback for longer sessions. Peer mentor way-forward dictation may routinely exceed this limit, causing silent truncation of transcribed content without user feedback.

Mitigation & Contingency

Mitigation: Implement session-chunking logic in NativeSpeechApiBridge that automatically restarts recognition before the limit is reached, preserving continuity via partial concatenation. Document the iOS 15 vs iOS 16 on-device recognition behaviour difference in code comments.

Contingency: If chunking causes user-visible interruptions, surface a non-blocking informational banner on iOS 15 devices informing users that very long dictation sessions may need to be broken into segments, and use PartialTranscriptionRepository to persist each chunk immediately.

high impact medium prob scope

On iOS, speech recognition permission can only be requested once. If the user denies the permission, the app cannot re-request it. A poor first-impression permission flow will permanently disable dictation for those users, impacting the Blindeforbundet blind-user base who rely on dictation most.

Mitigation & Contingency

Mitigation: Design the NativeSpeechApiBridge permission flow to show a clear pre-permission rationale screen before the OS dialog. Implement a graceful degradation path that hides the microphone button and shows a settings deep-link when permission is permanently denied.

Contingency: If users have already denied permission before the rationale screen is added, provide a settings deep-link in DictationScopeGuard's denial message directing users to iOS Settings > Privacy > Speech Recognition to re-enable manually.

medium impact low prob integration

The approved field IDs and screen routes configuration in DictationScopeGuard may fall out of sync with the actual report form schema as new fields are added by org administrators, silently blocking dictation on legitimately approved fields.

Mitigation & Contingency

Mitigation: Source the approved field configuration from the same org-field-config-loader used by the report form, rather than a hardcoded list. Add a developer-time assertion that logs a warning when a dictation-eligible field type is rendered but not in the approved routes map.

Contingency: Provide a runtime override mechanism in the scope guard that coordinators or admins can use to temporarily whitelist a field ID while the config is updated, with an automatic expiry.