#![forbid(unsafe_code)]
use chrono::{DateTime, Utc};
use kcode_audio_history_handoff::{Error as HandoffError, Handoff};
pub use kcode_audio_ingress::SpeakerReviewAudio;
use kcode_audio_ingress::{
AudioIngress, AudioInput, ChunkConfirmation, CorrectionPacket, ErrorKind as AudioErrorKind,
RecordingState, RecordingStatus,
};
pub use kcode_audio_session_view::{IngressPiece, Recording, SpeakerReview};
use kcode_session_history::{SessionHistory, SessionRecord};
use serde_json::Value;
use uuid::Uuid;
mod legacy_review;
#[derive(Clone, Debug)]
pub struct Config {
pub user_id: String,
pub effective_context_tokens: u64,
}
#[derive(Clone, Debug)]
pub struct RecordingInput {
pub bytes: Vec<u8>,
pub recorded_at: DateTime<Utc>,
pub original_filename: Option<String>,
}
#[derive(Clone, Debug)]
pub struct RecordingSubmission {
pub recording: Recording,
pub deduplicated: bool,
}
#[derive(Clone, Debug)]
pub struct RecordingHistory {
pub recording: Recording,
pub final_transcript: Option<String>,
pub correction_packet: Option<CorrectionPacket>,
pub pieces: Vec<IngressPiece>,
}
#[derive(Clone, Debug)]
pub struct RetryIngress {
pub piece_id: String,
pub expected_version: i64,
pub state: Option<Value>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
InvalidInput,
NotFound,
Conflict,
Internal,
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
message: String,
}
impl Error {
fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
}
}
fn invalid(message: impl Into<String>) -> Self {
Self::new(ErrorKind::InvalidInput, message)
}
fn conflict(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Conflict, message)
}
fn not_found() -> Self {
Self::new(
ErrorKind::NotFound,
"Audio recording or transcript piece not found.",
)
}
fn internal(error: impl std::fmt::Display) -> Self {
tracing::warn!(%error, "Audio session ingress operation failed");
Self::new(
ErrorKind::Internal,
"An unexpected audio session ingress error occurred.",
)
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for Error {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for Error {}
#[derive(Clone)]
pub struct Coordinator {
audio: AudioIngress,
handoff: Handoff,
user_id: String,
}
impl Coordinator {
pub fn new(
audio: AudioIngress,
history: SessionHistory,
config: Config,
) -> Result<Self, Error> {
let handoff = Handoff::new(
history,
config.user_id.clone(),
config.effective_context_tokens,
)
.map_err(handoff_error)?;
Ok(Self {
audio,
handoff,
user_id: config.user_id,
})
}
pub fn health(&self) -> Result<(), Error> {
self.audio.status().map_err(audio_error)?;
Ok(())
}
pub async fn submit(&self, input: RecordingInput) -> Result<RecordingSubmission, Error> {
let submission = self
.audio
.submit(AudioInput {
user_id: self.user_id.clone(),
bytes: input.bytes,
recorded_at: input.recorded_at,
original_filename: input.original_filename,
})
.await
.map_err(audio_error)?;
let (recordings, projections) = self.prepared_recordings().await?;
let (recording_status, projection) = recordings
.into_iter()
.zip(projections)
.find(|(recording, _)| recording.id == submission.recording_id)
.ok_or_else(Error::not_found)?;
validate_submission_owner(&recording_status, &self.user_id)?;
let view = kcode_audio_session_view::render(recording_status, projection);
Ok(RecordingSubmission {
recording: view.recording,
deduplicated: submission.deduplicated,
})
}
pub async fn recordings(&self) -> Result<Vec<Recording>, Error> {
let (recordings, projections) = self.prepared_recordings().await?;
Ok(recordings
.into_iter()
.zip(projections)
.map(|(recording, projection)| {
kcode_audio_session_view::render(recording, projection).recording
})
.collect())
}
pub async fn recording_by_sha256(&self, sha256: &str) -> Result<Recording, Error> {
if sha256.len() != 64 || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(Error::invalid(
"audio SHA-256 must contain exactly 64 hexadecimal characters",
));
}
let normalized = sha256.to_ascii_lowercase();
self.recordings()
.await?
.into_iter()
.find(|recording| recording.sha256 == normalized)
.ok_or_else(Error::not_found)
}
pub async fn recording_history(&self, recording_id: Uuid) -> Result<RecordingHistory, Error> {
let (recordings, projections) = self.prepared_recordings().await?;
let (recording_status, projection) = recordings
.into_iter()
.zip(projections)
.find(|(recording, _)| {
recording.id == recording_id && recording_belongs_to(recording, &self.user_id)
})
.ok_or_else(Error::not_found)?;
let final_transcript = match &recording_status.state {
RecordingState::Complete { transcript } => Some(transcript.clone()),
_ => None,
};
let correction_packet = recording_status.correction_packet.clone();
let view = kcode_audio_session_view::render(recording_status, projection);
Ok(RecordingHistory {
recording: view.recording,
final_transcript,
correction_packet,
pieces: view.pieces,
})
}
pub async fn speaker_review_audio(
&self,
recording_id: Uuid,
chunk_index: usize,
) -> Result<SpeakerReviewAudio, Error> {
let owned = self
.audio
.status()
.map_err(audio_error)?
.recordings
.into_iter()
.any(|recording| {
recording.id == recording_id && recording_belongs_to(&recording, &self.user_id)
});
if !owned {
return Err(Error::not_found());
}
let audio = self.audio.clone();
tokio::task::spawn_blocking(move || audio.speaker_review_audio(recording_id, chunk_index))
.await
.map_err(Error::internal)?
.map_err(audio_error)
}
pub fn known_speakers(&self) -> Result<Vec<String>, Error> {
self.audio.known_speakers().map_err(audio_error)
}
pub fn retry_recording(&self, recording_id: Uuid) -> Result<(), Error> {
let owned = self
.audio
.status()
.map_err(audio_error)?
.recordings
.into_iter()
.any(|recording| {
recording.id == recording_id && recording_belongs_to(&recording, &self.user_id)
});
if !owned {
return Err(Error::not_found());
}
self.audio.retry(recording_id).map_err(audio_error)
}
pub async fn confirm_speakers(
&self,
confirmation: ChunkConfirmation,
) -> Result<CorrectionPacket, Error> {
let recording_id = confirmation.recording_id;
let (recordings, _) = self.prepared_recordings().await?;
let recording = recordings
.into_iter()
.find(|recording| {
recording.id == recording_id && recording_belongs_to(recording, &self.user_id)
})
.ok_or_else(Error::not_found)?;
if let Some(packet) = recording
.correction_packet
.as_ref()
.filter(|packet| legacy_review::confirmation_matches(packet, &confirmation))
{
self.handoff
.synchronize(std::slice::from_ref(&recording))
.await
.map_err(handoff_error)?;
return Ok(packet.clone());
}
if self
.handoff
.has_ingress(recording_id)
.await
.map_err(handoff_error)?
{
return Err(Error::conflict(
"speaker labels are already bound to accepted transcript ingress",
));
}
let packet = self
.audio
.confirm_speakers(confirmation)
.map_err(audio_error)?;
let recording = self
.audio
.status()
.map_err(audio_error)?
.recordings
.into_iter()
.find(|recording| {
recording.id == recording_id && recording_belongs_to(recording, &self.user_id)
})
.ok_or_else(Error::not_found)?;
self.handoff
.synchronize(std::slice::from_ref(&recording))
.await
.map_err(handoff_error)?;
Ok(packet)
}
pub async fn retry_ingress(&self, input: RetryIngress) -> Result<SessionRecord, Error> {
let recordings = self.audio.status().map_err(audio_error)?.recordings;
self.handoff
.retry(
&recordings,
&input.piece_id,
input.expected_version,
input.state,
)
.await
.map_err(handoff_error)
}
pub async fn synchronize_completed_transcripts(&self) -> Result<(), Error> {
let mut recordings = self.owned_recordings()?;
if legacy_review::has_candidates(&recordings) {
let projections = self.project_recordings(&recordings).await?;
if self
.resolve_legacy_reviews(&recordings, &projections)
.await?
{
recordings = self.owned_recordings()?;
}
}
self.handoff
.synchronize(&recordings)
.await
.map_err(handoff_error)
}
}
fn recording_belongs_to(recording: &RecordingStatus, user_id: &str) -> bool {
recording.user_id == user_id
}
fn validate_submission_owner(recording: &RecordingStatus, user_id: &str) -> Result<(), Error> {
if recording_belongs_to(recording, user_id) {
Ok(())
} else {
Err(Error::conflict(
"identical audio is already attributed to another user",
))
}
}
fn audio_error(error: kcode_audio_ingress::Error) -> Error {
match error.kind() {
AudioErrorKind::InvalidInput => Error::new(ErrorKind::InvalidInput, error.to_string()),
AudioErrorKind::NotFound => Error::new(ErrorKind::NotFound, error.to_string()),
AudioErrorKind::Conflict => Error::new(ErrorKind::Conflict, error.to_string()),
AudioErrorKind::Internal => Error::internal(error),
}
}
fn handoff_error(error: HandoffError) -> Error {
match error {
HandoffError::InvalidInput(message) => Error::new(ErrorKind::InvalidInput, message),
HandoffError::NotFound(message) => Error::new(ErrorKind::NotFound, message),
HandoffError::Conflict(message) => Error::new(ErrorKind::Conflict, message),
HandoffError::Internal(message) => Error::new(ErrorKind::Internal, message),
}
}
#[cfg(test)]
mod tests {
use super::*;
use kcode_audio_ingress::{
ChunkConfirmation, ConfirmationState, ObservationConfirmation, SpeakerResolution,
};
fn completed_recording_for_user(
user_id: impl Into<String>,
transcript: impl Into<String>,
) -> RecordingStatus {
let now = Utc::now();
RecordingStatus {
id: Uuid::new_v4(),
user_id: user_id.into(),
sha256: "0".repeat(64),
original_filename: "meeting.final.WAV".into(),
size_bytes: 42,
recorded_at: now,
received_at: now,
transcription_model: "transcription-model".into(),
reconciliation_model: "reconciliation-model".into(),
reconciliation_reasoning: "xhigh".into(),
state: RecordingState::Complete {
transcript: transcript.into(),
},
correction_packet: None,
}
}
fn with_speaker_packet(
mut recording: RecordingStatus,
confirmed_name: Option<&str>,
) -> RecordingStatus {
recording.correction_packet = Some(CorrectionPacket {
recording_id: recording.id,
user_id: recording.user_id.clone(),
sha256: recording.sha256.clone(),
original_filename: recording.original_filename.clone(),
size_bytes: recording.size_bytes,
recorded_at: recording.recorded_at,
chunk_count: 1,
chunks: vec![kcode_audio_ingress::CorrectionChunk {
chunk_index: 0,
chunk_count: 1,
audio_start_ms: 0,
audio_end_ms: 1_000,
raw_gemini_response: "raw".into(),
parsed: kcode_audio_ingress::ParsedChunk {
clip_valid: true,
clip_validity_reason: None,
speakers: Vec::new(),
},
observations: vec![kcode_audio_ingress::CorrectionObservation {
local_label: "Speaker A".into(),
speaker_ordinal: 0,
observation_key: kcode_audio_ingress::ObservationKey {
object_id: format!(
"kcode-audio-ingress/recording/{}/chunk/0",
recording.id
),
piece_index: 0,
},
candidate: None,
resolution: confirmed_name.map(|name| SpeakerResolution::Known {
full_name: name.into(),
}),
}],
signed_off: true,
}],
confirmation_state: ConfirmationState::Confirmed,
});
recording
}
#[test]
fn recording_filter_is_exactly_scoped_to_the_configured_user() {
let own = completed_recording_for_user("own-user", "Own transcript");
let foreign = completed_recording_for_user("foreign-user", "Foreign transcript");
let visible = [own.clone(), foreign]
.into_iter()
.filter(|recording| recording_belongs_to(recording, "own-user"))
.collect::<Vec<_>>();
assert_eq!(visible.len(), 1);
assert_eq!(visible[0].id, own.id);
}
#[test]
fn cross_user_sha_deduplication_fails_closed() {
let mut own = completed_recording_for_user("own-user", "Own transcript");
own.sha256 = "a".repeat(64);
let mut foreign = completed_recording_for_user("foreign-user", "Foreign transcript");
foreign.sha256 = own.sha256.clone();
assert!(validate_submission_owner(&own, "own-user").is_ok());
let error = validate_submission_owner(&foreign, "own-user").unwrap_err();
assert_eq!(error.kind(), ErrorKind::Conflict);
assert_eq!(
error.message(),
"identical audio is already attributed to another user"
);
}
#[test]
fn confirmed_label_retries_must_match_the_complete_mapping() {
let recording = with_speaker_packet(
completed_recording_for_user("user", "Transcript"),
Some("Human Choice"),
);
let packet = recording.correction_packet.as_ref().unwrap();
let key = packet.chunks[0].observations[0].observation_key.clone();
let matching = ChunkConfirmation {
recording_id: recording.id,
chunk_index: 0,
observations: vec![ObservationConfirmation {
observation_key: key.clone(),
resolution: SpeakerResolution::Known {
full_name: "Human Choice".into(),
},
}],
};
let conflicting = ChunkConfirmation {
recording_id: recording.id,
chunk_index: 0,
observations: vec![ObservationConfirmation {
observation_key: key,
resolution: SpeakerResolution::Known {
full_name: "Different Choice".into(),
},
}],
};
let incomplete = ChunkConfirmation {
recording_id: recording.id,
chunk_index: 0,
observations: Vec::new(),
};
assert!(legacy_review::confirmation_matches(packet, &matching));
assert!(!legacy_review::confirmation_matches(packet, &conflicting));
assert!(!legacy_review::confirmation_matches(packet, &incomplete));
}
#[test]
fn handoff_errors_preserve_category_and_message() {
let cases = [
(
HandoffError::InvalidInput("invalid".into()),
ErrorKind::InvalidInput,
"invalid",
),
(
HandoffError::NotFound("missing".into()),
ErrorKind::NotFound,
"missing",
),
(
HandoffError::Conflict("conflict".into()),
ErrorKind::Conflict,
"conflict",
),
(
HandoffError::Internal("raw storage message".into()),
ErrorKind::Internal,
"raw storage message",
),
];
for (source, kind, message) in cases {
let error = handoff_error(source);
assert_eq!(error.kind(), kind);
assert_eq!(error.message(), message);
}
}
}