package com.sptmobile.voice import kotlinx.coroutines.flow.Flow /** * One spooled dictation. [msgId] is minted ONCE at enqueue and stored, so * every forward attempt across restarts rides the same exact-dedup key * (ruling 8 — msg-id is the own-send dedup axis; a retry after a lost reply * must not mint a new identity). */ data class VoiceRow( val seq: Long, val msgId: String, val transcription: String, val recordedAtMs: Long, val spooledAtMs: Long, ) // [impl->REQ-VOICE-PIPE] /** * The phone spool (DESIGN.md §Voice pipe semantics, ruling 6): a durable * FIFO between the webhook receiver and the forwarder. All methods except * [pending] are blocking — the receiver calls [enqueue] on its socket thread * (the row must be durable BEFORE the 200 is written, * REQ-HAZARD-DICTATION-LOSS) and the forwarder wraps calls in an IO * dispatcher. Interface (not the Room impl) so forwarder/receiver semantics * stay JVM-unit-testable. */ interface VoiceSpool { /** Durably persist a dictation; returns the stored row (msg-id minted). */ fun enqueue(transcription: String, recordedAtMs: Long): VoiceRow /** The FIFO head, or null when the spool is empty. */ fun oldest(): VoiceRow? /** Remove a consumed row (the forwarder got an ANSWER for it). */ fun remove(seq: Long) /** Live pending count — the UI badge + the forwarder trigger. */ val pending: Flow }