Skip to main content

eredu_runtime/
realtime.rs

1//! Atomic runtime ownership for one realtime model-generation transition.
2//!
3//! Architecture equations decide what work to submit. This module owns the
4//! portable publication boundary across model/cache state, delayed-frame
5//! scheduling, sequential sampler state, and backend randomness.
6
7use crate::{
8    Sampler, SamplingBackend, SequentialDecisionDriver, SequentialDecisionError,
9    SequentialDecisionPlan, SequentialDecisionPlanError, SubmissionBackend,
10};
11use eredu_core::{
12    scheduler::SemanticStateTransaction, Completion, RealtimeFrameScheduleState,
13    RealtimeInputFrame, RealtimeScheduleError, RealtimeSpeechConfig,
14};
15use std::marker::PhantomData;
16
17/// Canonical composite state for realtime model generation.
18///
19/// `C` is the exact completion type returned by a concrete
20/// [`SubmissionBackend`]. It is carried at the type level so a branch cannot
21/// be published using an unrelated completion kind.
22#[derive(Debug)]
23pub struct RealtimeGenerationState<M, S, R, C> {
24    model_state: M,
25    schedule_state: RealtimeFrameScheduleState,
26    samplers: Vec<S>,
27    random_state: Option<R>,
28    completion: PhantomData<fn() -> C>,
29}
30
31/// Unpublished branch of every mutable component in one realtime transition.
32#[derive(Debug)]
33pub struct RealtimeGenerationBranch<MB, S, R, C> {
34    model_state: MB,
35    schedule_state: RealtimeFrameScheduleState,
36    samplers: Vec<S>,
37    random_state: Option<R>,
38    completion: Option<C>,
39}
40
41/// Additive execution contract for architectures that consume realtime frames.
42///
43/// The causal-text base model contract remains unchanged. Realtime composition
44/// implements this extension only when frame ingress is part of execution.
45pub trait RealtimeFrameTransition<MB, S, R, C> {
46    /// Output causally produced by one frame transition.
47    type Output;
48    /// Architecture or mechanism failure before publication.
49    type Error;
50
51    /// Consumes the portable ingress frame, mutates the unpublished branch,
52    /// and returns the exact completion associated with the submitted work.
53    fn execute(
54        &mut self,
55        frame: &RealtimeInputFrame,
56        branch: &mut RealtimeGenerationBranch<MB, S, R, C>,
57    ) -> Result<(Self::Output, C), Self::Error>;
58}
59
60/// Failure while executing or atomically publishing one realtime frame.
61#[derive(Debug, thiserror::Error)]
62#[non_exhaustive]
63pub enum RealtimeFrameExecutionError<TransitionError, TransactionError> {
64    /// Frame ingress or architecture execution failed before publication.
65    #[error("realtime frame transition failed")]
66    Transition(#[source] TransitionError),
67    /// The returned completion could not be attached to the exact branch.
68    #[error(transparent)]
69    CompletionAttachment(#[from] RealtimeCompletionAttachmentError),
70    /// Exact completion or atomic state publication failed.
71    #[error("realtime frame publication failed")]
72    Publication(#[source] TransactionError),
73}
74
75/// Failure to attach exact backend completion evidence to a branch.
76#[derive(Debug, Clone, Copy, Eq, PartialEq, thiserror::Error)]
77#[non_exhaustive]
78pub enum RealtimeCompletionAttachmentError {
79    /// A transition represents one exact submission and already has evidence.
80    #[error("realtime generation branch already has an exact submission completion")]
81    AlreadyAttached,
82}
83
84/// Invalid composite branch construction or publication.
85#[derive(Debug, thiserror::Error)]
86#[non_exhaustive]
87pub enum RealtimeGenerationTransactionError<ModelError, CompletionError> {
88    /// The model/cache transaction could not branch or publish.
89    #[error("realtime model-state transaction failed: {0}")]
90    Model(#[source] ModelError),
91    /// The portable delayed-frame schedule identity did not match.
92    #[error(transparent)]
93    Schedule(#[from] RealtimeScheduleError),
94    /// Sampler count must equal text plus every depth-codebook decision.
95    #[error("realtime generation requires {expected} sampler states, received {actual}")]
96    SamplerCardinality {
97        /// Required text-plus-depth prediction count.
98        expected: usize,
99        /// Supplied sampler-state count.
100        actual: usize,
101    },
102    /// No exact backend completion was attached to the proposed branch.
103    #[error("realtime generation branch has no exact submission completion")]
104    MissingCompletion,
105    /// The exact backend submission has not completed yet.
106    #[error("realtime generation submission is still pending")]
107    CompletionPending,
108    /// Exact completion observation or successful waiting failed.
109    #[error("realtime generation submission failed: {0}")]
110    Completion(#[source] CompletionError),
111}
112
113impl<M, S, R, C> RealtimeGenerationState<M, S, R, C>
114where
115    M: SemanticStateTransaction,
116    M::Error: 'static,
117    C: Completion,
118{
119    /// Creates canonical state bound to one exact normalized schedule.
120    pub fn new(
121        model_state: M,
122        schedule: RealtimeSpeechConfig,
123        samplers: Vec<S>,
124        random_state: Option<R>,
125    ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
126        Self::from_parts(
127            model_state,
128            &schedule,
129            RealtimeFrameScheduleState::new(schedule.clone()),
130            samplers,
131            random_state,
132        )
133    }
134
135    /// Validates and adopts an existing portable schedule state.
136    ///
137    /// This constructor is useful when resuming in-memory state: the state must
138    /// carry the same complete schedule identity, not merely equal dimensions.
139    pub fn from_parts(
140        model_state: M,
141        schedule: &RealtimeSpeechConfig,
142        schedule_state: RealtimeFrameScheduleState,
143        samplers: Vec<S>,
144        random_state: Option<R>,
145    ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
146        schedule_state.validate_schedule(schedule)?;
147        validate_sampler_cardinality(schedule, samplers.len())?;
148        Ok(Self {
149            model_state,
150            schedule_state,
151            samplers,
152            random_state,
153            completion: PhantomData,
154        })
155    }
156
157    /// Borrows canonical model/cache state.
158    pub const fn model_state(&self) -> &M {
159        &self.model_state
160    }
161
162    /// Borrows canonical delayed-frame state.
163    pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
164        &self.schedule_state
165    }
166
167    /// Borrows canonical sampler states in text-plus-depth order.
168    pub fn samplers(&self) -> &[S] {
169        &self.samplers
170    }
171
172    /// Borrows canonical backend random state.
173    pub const fn random_state(&self) -> Option<&R> {
174        self.random_state.as_ref()
175    }
176
177    /// Replaces backend randomness while the canonical request is idle.
178    pub fn set_random_state(&mut self, random_state: Option<R>) {
179        self.random_state = random_state;
180    }
181
182    /// Replaces sampler state while the canonical request is idle.
183    pub fn set_samplers(
184        &mut self,
185        samplers: Vec<S>,
186    ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>> {
187        validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())?;
188        self.samplers = samplers;
189        Ok(())
190    }
191
192    /// Executes one ingress-dependent transition and publishes every mutable
193    /// component only after its exact completion succeeds.
194    #[allow(
195        clippy::type_complexity,
196        reason = "the signature preserves the exact transition, model, and completion error types"
197    )]
198    pub fn execute_frame_transition<T>(
199        &mut self,
200        frame: &RealtimeInputFrame,
201        transition: &mut T,
202    ) -> Result<
203        T::Output,
204        RealtimeFrameExecutionError<
205            T::Error,
206            RealtimeGenerationTransactionError<M::Error, C::Error>,
207        >,
208    >
209    where
210        S: Clone,
211        R: Clone,
212        T: RealtimeFrameTransition<M::Branch, S, R, C>,
213    {
214        let mut branch = SemanticStateTransaction::branch(self)
215            .map_err(RealtimeFrameExecutionError::Publication)?;
216        let (output, completion) = transition
217            .execute(frame, &mut branch)
218            .map_err(RealtimeFrameExecutionError::Transition)?;
219        branch.attach_submission_completion(completion)?;
220        SemanticStateTransaction::commit_branch(self, branch)
221            .map_err(RealtimeFrameExecutionError::Publication)?;
222        Ok(output)
223    }
224
225    /// Publishes a branch only after the concrete backend's exact completion
226    /// has reported completion and successful waiting.
227    ///
228    /// The backend bound proves that `C` is a [`SubmissionBackend::Completion`]
229    /// rather than architecture-owned or family-specific completion metadata.
230    pub fn commit_submission_branch<B>(
231        &mut self,
232        branch: RealtimeGenerationBranch<M::Branch, S, R, C>,
233    ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>>
234    where
235        B: SubmissionBackend<Completion = C>,
236        S: Clone,
237        R: Clone,
238    {
239        self.commit_branch(branch)
240    }
241}
242
243impl<MB, S, R, C> RealtimeGenerationBranch<MB, S, R, C> {
244    /// Mutably borrows the transition-local model/cache state.
245    pub fn model_state_mut(&mut self) -> &mut MB {
246        &mut self.model_state
247    }
248
249    /// Borrows the transition-local delayed-frame state.
250    pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
251        &self.schedule_state
252    }
253
254    /// Mutably borrows the transition-local delayed-frame state.
255    pub fn schedule_state_mut(&mut self) -> &mut RealtimeFrameScheduleState {
256        &mut self.schedule_state
257    }
258
259    /// Borrows transition-local sampler states in text-plus-depth order.
260    pub fn samplers(&self) -> &[S] {
261        &self.samplers
262    }
263
264    /// Borrows transition-local backend random state.
265    pub const fn random_state(&self) -> Option<&R> {
266        self.random_state.as_ref()
267    }
268
269    /// Attaches the one exact completion returned by backend submission.
270    pub fn attach_submission_completion(
271        &mut self,
272        completion: C,
273    ) -> Result<(), RealtimeCompletionAttachmentError> {
274        if self.completion.is_some() {
275            return Err(RealtimeCompletionAttachmentError::AlreadyAttached);
276        }
277        self.completion = Some(completion);
278        Ok(())
279    }
280
281    /// Creates the existing sequential decision driver from cloned branch-local
282    /// sampler and random state.
283    ///
284    /// The driver remains the sole state machine for forced, sampled, and
285    /// diagnostic decisions. State is adopted back only after it finishes.
286    pub fn decision_driver<B>(
287        &self,
288        plan: SequentialDecisionPlan<B::Token>,
289        temperatures: Vec<f32>,
290    ) -> Result<SequentialDecisionDriver<B, S>, SequentialDecisionPlanError>
291    where
292        B: SamplingBackend<RandomState = R>,
293        S: Sampler<B> + Clone,
294        R: Clone,
295    {
296        SequentialDecisionDriver::new(
297            plan,
298            self.samplers.clone(),
299            temperatures,
300            self.random_state.clone(),
301        )
302    }
303
304    /// Atomically adopts sampler and random state from a completed existing
305    /// sequential decision driver.
306    ///
307    /// An incomplete driver returns an error and leaves this branch unchanged.
308    pub fn adopt_decision_driver<B>(
309        &mut self,
310        driver: SequentialDecisionDriver<B, S>,
311    ) -> Result<(), SequentialDecisionError<B::Error>>
312    where
313        B: SamplingBackend<RandomState = R>,
314        S: Sampler<B>,
315    {
316        let (samplers, random_state) = driver.finish_into_sampling_state()?;
317        self.samplers = samplers;
318        self.random_state = random_state;
319        Ok(())
320    }
321}
322
323impl<M, S, R, C> SemanticStateTransaction for RealtimeGenerationState<M, S, R, C>
324where
325    M: SemanticStateTransaction,
326    M::Error: 'static,
327    S: Clone,
328    R: Clone,
329    C: Completion,
330{
331    type Branch = RealtimeGenerationBranch<M::Branch, S, R, C>;
332    type Error = RealtimeGenerationTransactionError<M::Error, C::Error>;
333
334    fn branch(&self) -> Result<Self::Branch, Self::Error> {
335        Ok(RealtimeGenerationBranch {
336            model_state: self.model_state.branch().map_err(Self::Error::Model)?,
337            schedule_state: self.schedule_state.branch()?,
338            samplers: self.samplers.clone(),
339            random_state: self.random_state.clone(),
340            completion: None,
341        })
342    }
343
344    fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
345        let RealtimeGenerationBranch {
346            model_state,
347            schedule_state,
348            samplers,
349            random_state,
350            completion,
351        } = branch;
352
353        let rollback = |model_state, error| match M::discard_branch(model_state) {
354            Ok(()) => error,
355            Err(error) => Self::Error::Model(error),
356        };
357        let Some(completion) = completion else {
358            return Err(rollback(model_state, Self::Error::MissingCompletion));
359        };
360        let complete = match completion.is_complete() {
361            Ok(complete) => complete,
362            Err(error) => {
363                return Err(rollback(model_state, Self::Error::Completion(error)));
364            }
365        };
366        if !complete {
367            return Err(rollback(model_state, Self::Error::CompletionPending));
368        }
369        if let Err(error) = completion.wait() {
370            return Err(rollback(model_state, Self::Error::Completion(error)));
371        }
372        if let Err(error) = self
373            .schedule_state
374            .validate_schedule(schedule_state.schedule())
375        {
376            return Err(rollback(model_state, error.into()));
377        }
378        if let Err(error) =
379            validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())
380        {
381            return Err(rollback(model_state, error));
382        }
383
384        self.model_state
385            .commit_branch(model_state)
386            .map_err(Self::Error::Model)?;
387        self.schedule_state.commit_branch(schedule_state)?;
388        self.samplers = samplers;
389        self.random_state = random_state;
390        Ok(())
391    }
392
393    fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
394        M::discard_branch(branch.model_state).map_err(Self::Error::Model)
395    }
396
397    fn permits_parallel_branches(&self) -> bool {
398        self.model_state.permits_parallel_branches()
399    }
400}
401
402fn validate_sampler_cardinality<ModelError, CompletionError>(
403    schedule: &RealtimeSpeechConfig,
404    actual: usize,
405) -> Result<(), RealtimeGenerationTransactionError<ModelError, CompletionError>> {
406    let expected = schedule.depth_audio_codebooks().checked_add(1).ok_or(
407        RealtimeGenerationTransactionError::SamplerCardinality {
408            expected: usize::MAX,
409            actual,
410        },
411    )?;
412    if actual != expected {
413        return Err(RealtimeGenerationTransactionError::SamplerCardinality { expected, actual });
414    }
415    Ok(())
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use crate::{PenaltyConfig, PredictionDirective};
422    use eredu_core::{
423        scheduler::SemanticStateTransaction, RealtimeFrameConvention, RealtimeFrameForcing,
424        RealtimeInputFrame, TokenFilter,
425    };
426    use std::{cell::Cell, convert::Infallible, fmt, rc::Rc};
427
428    #[derive(Debug, Clone, Eq, PartialEq)]
429    struct ModelState {
430        model_step: i32,
431        cache_offset: i32,
432    }
433
434    #[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
435    #[error("model transaction failed")]
436    struct ModelError;
437
438    impl SemanticStateTransaction for ModelState {
439        type Branch = Self;
440        type Error = ModelError;
441
442        fn branch(&self) -> Result<Self::Branch, Self::Error> {
443            Ok(self.clone())
444        }
445
446        fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
447            *self = branch;
448            Ok(())
449        }
450    }
451
452    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
453    enum CompletionOutcome {
454        Success,
455        Pending,
456        Failure,
457    }
458
459    #[derive(Debug, Clone)]
460    struct MockCompletion {
461        outcome: CompletionOutcome,
462        waits: Rc<Cell<usize>>,
463    }
464
465    #[derive(Debug, Clone, Eq, PartialEq)]
466    struct CompletionError;
467
468    impl fmt::Display for CompletionError {
469        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
470            formatter.write_str("mock submission failed")
471        }
472    }
473
474    impl std::error::Error for CompletionError {}
475
476    impl MockCompletion {
477        fn new(outcome: CompletionOutcome) -> (Self, Rc<Cell<usize>>) {
478            let waits = Rc::new(Cell::new(0));
479            (
480                Self {
481                    outcome,
482                    waits: waits.clone(),
483                },
484                waits,
485            )
486        }
487    }
488
489    impl Completion for MockCompletion {
490        type Error = CompletionError;
491
492        fn is_complete(&self) -> Result<bool, Self::Error> {
493            Ok(self.outcome != CompletionOutcome::Pending)
494        }
495
496        fn wait(&self) -> Result<(), Self::Error> {
497            self.waits.set(self.waits.get() + 1);
498            match self.outcome {
499                CompletionOutcome::Success => Ok(()),
500                CompletionOutcome::Failure => Err(CompletionError),
501                CompletionOutcome::Pending => panic!("pending completion must not be waited"),
502            }
503        }
504    }
505
506    struct Backend;
507
508    impl SamplingBackend for Backend {
509        type Logits = i32;
510        type Token = i32;
511        type RandomState = i32;
512        type Context = ();
513        type Error = String;
514
515        fn error(message: String) -> Self::Error {
516            message
517        }
518
519        fn validate_token(
520            token: &Self::Token,
521            domain: crate::TokenDomain,
522            _: &Self::Context,
523        ) -> Result<Self::Token, Self::Error> {
524            usize::try_from(*token)
525                .ok()
526                .filter(|token| *token < domain.cardinality())
527                .map(|_| *token)
528                .ok_or_else(|| "token is outside its decision domain".into())
529        }
530
531        fn scale_temperature(
532            logits: &Self::Logits,
533            _: f32,
534            _: &Self::Context,
535        ) -> Result<Self::Logits, Self::Error> {
536            Ok(*logits)
537        }
538
539        fn apply_penalties(
540            logits: &Self::Logits,
541            _: &[u32],
542            _: PenaltyConfig,
543            _: &Self::Context,
544        ) -> Result<Self::Logits, Self::Error> {
545            Ok(*logits)
546        }
547
548        fn apply_top_k(
549            logits: Self::Logits,
550            _: i32,
551            _: &Self::Context,
552        ) -> Result<Self::Logits, Self::Error> {
553            Ok(logits)
554        }
555
556        fn apply_top_p(
557            logits: Self::Logits,
558            _: f32,
559            _: &Self::Context,
560        ) -> Result<Self::Logits, Self::Error> {
561            Ok(logits)
562        }
563
564        fn apply_min_p(
565            logits: Self::Logits,
566            _: f32,
567            _: &Self::Context,
568        ) -> Result<Self::Logits, Self::Error> {
569            Ok(logits)
570        }
571
572        fn apply_token_filter(
573            logits: &Self::Logits,
574            _: &TokenFilter,
575            _: &Self::Context,
576        ) -> Result<Self::Logits, Self::Error> {
577            Ok(*logits)
578        }
579
580        fn apply_mirostat(
581            logits: &Self::Logits,
582            _: &[u32],
583            _: PenaltyConfig,
584            _: f32,
585            _: f32,
586            _: &Self::Context,
587        ) -> Result<Self::Logits, Self::Error> {
588            Ok(*logits)
589        }
590
591        fn sample_raw(
592            logits: &Self::Logits,
593            _: f32,
594            random: Option<&mut Self::RandomState>,
595            _: &Self::Context,
596        ) -> Result<Self::Token, Self::Error> {
597            if let Some(random) = random {
598                *random += 10;
599            }
600            Ok(*logits)
601        }
602
603        fn sample_processed(
604            logits: &Self::Logits,
605            temperature: f32,
606            random: Option<&mut Self::RandomState>,
607            context: &Self::Context,
608        ) -> Result<Self::Token, Self::Error> {
609            Self::sample_raw(logits, temperature, random, context)
610        }
611
612        fn token_id(token: &Self::Token, _: &Self::Context) -> Result<u32, Self::Error> {
613            u32::try_from(*token).map_err(|error| error.to_string())
614        }
615
616        fn token_probability(
617            _: &Self::Logits,
618            _: u32,
619            _: &Self::Context,
620        ) -> Result<f32, Self::Error> {
621            Ok(1.0)
622        }
623    }
624
625    #[derive(Debug, Clone, Eq, PartialEq)]
626    struct StatefulSampler(i32);
627
628    impl Sampler<Backend> for StatefulSampler {
629        fn sample(
630            &mut self,
631            logits: &i32,
632            _: f32,
633            random: Option<&mut i32>,
634            _: &(),
635        ) -> Result<i32, String> {
636            self.0 += 1;
637            if let Some(random) = random {
638                *random += 10;
639            }
640            Ok(*logits + self.0)
641        }
642    }
643
644    type State = RealtimeGenerationState<ModelState, StatefulSampler, i32, MockCompletion>;
645
646    fn schedule() -> RealtimeSpeechConfig {
647        RealtimeSpeechConfig::new(
648            2,
649            1,
650            1,
651            1,
652            100,
653            64,
654            RealtimeFrameConvention::FeedbackAlignedHistory,
655            vec![0, 0, 1],
656        )
657        .unwrap()
658    }
659
660    fn state() -> State {
661        State::new(
662            ModelState {
663                model_step: 1,
664                cache_offset: 2,
665            },
666            schedule(),
667            vec![StatefulSampler(0), StatefulSampler(5)],
668            Some(7),
669        )
670        .unwrap()
671    }
672
673    fn mutate_every_component(
674        frame: &RealtimeInputFrame,
675        branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
676    ) {
677        branch.model_state_mut().model_step = frame.input_audio_tokens()[0];
678        branch.model_state_mut().cache_offset = frame.forced_text_tokens().unwrap()[0];
679        branch
680            .schedule_state_mut()
681            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
682            .unwrap();
683        let plan = SequentialDecisionPlan::new(
684            [PredictionDirective::Sample, PredictionDirective::Sample],
685            false,
686            false,
687        )
688        .unwrap();
689        let mut driver = branch
690            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
691            .unwrap();
692        assert_eq!(
693            driver
694                .resolve(0, &10, crate::TokenDomain::new(100), &())
695                .unwrap(),
696            11
697        );
698        assert_eq!(
699            driver
700                .resolve(1, &20, crate::TokenDomain::new(100), &())
701                .unwrap(),
702            26
703        );
704        branch.adopt_decision_driver(driver).unwrap();
705    }
706
707    struct FrameTransition {
708        outcome: CompletionOutcome,
709        waits: Rc<Cell<usize>>,
710    }
711
712    impl RealtimeFrameTransition<ModelState, StatefulSampler, i32, MockCompletion> for FrameTransition {
713        type Output = i32;
714        type Error = Infallible;
715
716        fn execute(
717            &mut self,
718            frame: &RealtimeInputFrame,
719            branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
720        ) -> Result<(Self::Output, MockCompletion), Self::Error> {
721            mutate_every_component(frame, branch);
722            let output = branch.model_state.model_step + branch.model_state.cache_offset;
723            Ok((
724                output,
725                MockCompletion {
726                    outcome: self.outcome,
727                    waits: Rc::clone(&self.waits),
728                },
729            ))
730        }
731    }
732
733    #[test]
734    fn realtime_frame_extension_publishes_ingress_and_state_atomically() {
735        let mut state = state();
736        let waits = Rc::new(Cell::new(0));
737        let mut transition = FrameTransition {
738            outcome: CompletionOutcome::Success,
739            waits: Rc::clone(&waits),
740        };
741        let output = state
742            .execute_frame_transition(
743                &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
744                &mut transition,
745            )
746            .unwrap();
747
748        assert_eq!(waits.get(), 1);
749        assert_eq!(output, 19);
750        assert_eq!(
751            state.model_state(),
752            &ModelState {
753                model_step: 9,
754                cache_offset: 10
755            }
756        );
757        assert_eq!(state.schedule_state().frontier(), 1);
758        assert_eq!(state.samplers(), [StatefulSampler(1), StatefulSampler(6)]);
759        assert_eq!(state.random_state(), Some(&27));
760    }
761
762    #[test]
763    fn failed_completion_rolls_back_every_component() {
764        let mut state = state();
765        let waits = Rc::new(Cell::new(0));
766        let mut transition = FrameTransition {
767            outcome: CompletionOutcome::Failure,
768            waits: Rc::clone(&waits),
769        };
770        assert!(matches!(
771            state.execute_frame_transition(
772                &RealtimeInputFrame::new(1, vec![40]).with_forced_text(vec![50]),
773                &mut transition,
774            ),
775            Err(RealtimeFrameExecutionError::Publication(
776                RealtimeGenerationTransactionError::Completion(_)
777            ))
778        ));
779
780        assert_eq!(waits.get(), 1);
781        assert_eq!(
782            state.model_state(),
783            &ModelState {
784                model_step: 1,
785                cache_offset: 2
786            }
787        );
788        assert_eq!(state.schedule_state().frontier(), 0);
789        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
790        assert_eq!(state.random_state(), Some(&7));
791    }
792
793    #[test]
794    fn pending_completion_is_not_waited_or_published() {
795        let mut state = state();
796        let mut branch = state.branch().unwrap();
797        mutate_every_component(
798            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
799            &mut branch,
800        );
801        let (completion, waits) = MockCompletion::new(CompletionOutcome::Pending);
802        branch.attach_submission_completion(completion).unwrap();
803        assert!(matches!(
804            state.commit_branch(branch),
805            Err(RealtimeGenerationTransactionError::CompletionPending)
806        ));
807        assert_eq!(waits.get(), 0);
808        assert_eq!(
809            state.model_state(),
810            &ModelState {
811                model_step: 1,
812                cache_offset: 2
813            }
814        );
815        assert_eq!(state.schedule_state().frontier(), 0);
816        assert_eq!(state.random_state(), Some(&7));
817    }
818
819    #[test]
820    fn discard_leaves_canonical_state_unchanged() {
821        let state = state();
822        let mut branch = state.branch().unwrap();
823        mutate_every_component(
824            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
825            &mut branch,
826        );
827        State::discard_branch(branch).unwrap();
828        assert_eq!(
829            state.model_state(),
830            &ModelState {
831                model_step: 1,
832                cache_offset: 2
833            }
834        );
835        assert_eq!(state.schedule_state().frontier(), 0);
836        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
837        assert_eq!(state.random_state(), Some(&7));
838    }
839
840    #[test]
841    fn composite_discard_and_failed_commit_delegate_model_rollback() {
842        #[derive(Debug, Clone)]
843        struct TrackingModel(Rc<Cell<usize>>);
844
845        impl SemanticStateTransaction for TrackingModel {
846            type Branch = Self;
847            type Error = ModelError;
848
849            fn branch(&self) -> Result<Self::Branch, Self::Error> {
850                Ok(self.clone())
851            }
852
853            fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
854                *self = branch;
855                Ok(())
856            }
857
858            fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
859                branch.0.set(branch.0.get() + 1);
860                Ok(())
861            }
862        }
863
864        type TrackingState =
865            RealtimeGenerationState<TrackingModel, StatefulSampler, i32, MockCompletion>;
866        let rollbacks = Rc::new(Cell::new(0));
867        let mut state = TrackingState::new(
868            TrackingModel(rollbacks.clone()),
869            schedule(),
870            vec![StatefulSampler(0), StatefulSampler(0)],
871            None,
872        )
873        .unwrap();
874
875        TrackingState::discard_branch(state.branch().unwrap()).unwrap();
876        assert_eq!(rollbacks.get(), 1);
877
878        let missing_completion = state.branch().unwrap();
879        assert!(matches!(
880            state.commit_branch(missing_completion),
881            Err(RealtimeGenerationTransactionError::MissingCompletion)
882        ));
883        assert_eq!(rollbacks.get(), 2);
884    }
885
886    #[test]
887    fn missing_completion_cannot_publish() {
888        let mut state = state();
889        let mut branch = state.branch().unwrap();
890        mutate_every_component(
891            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
892            &mut branch,
893        );
894        assert!(matches!(
895            state.commit_branch(branch),
896            Err(RealtimeGenerationTransactionError::MissingCompletion)
897        ));
898        assert_eq!(state.schedule_state().frontier(), 0);
899    }
900
901    #[test]
902    fn schedule_identity_and_sampler_cardinality_are_exact() {
903        let other = RealtimeSpeechConfig::new(
904            2,
905            1,
906            1,
907            1,
908            100,
909            64,
910            RealtimeFrameConvention::AbsoluteDelayedSlots,
911            vec![0, 0, 1],
912        )
913        .unwrap();
914        assert!(matches!(
915            State::from_parts(
916                ModelState {
917                    model_step: 1,
918                    cache_offset: 2,
919                },
920                &schedule(),
921                RealtimeFrameScheduleState::new(other),
922                vec![StatefulSampler(0), StatefulSampler(0)],
923                Some(0),
924            ),
925            Err(RealtimeGenerationTransactionError::Schedule(
926                RealtimeScheduleError::ScheduleMismatch
927            ))
928        ));
929        assert!(matches!(
930            State::new(
931                ModelState {
932                    model_step: 1,
933                    cache_offset: 2,
934                },
935                schedule(),
936                vec![StatefulSampler(0)],
937                Some(0),
938            ),
939            Err(RealtimeGenerationTransactionError::SamplerCardinality {
940                expected: 2,
941                actual: 1
942            })
943        ));
944
945        let mut state = state();
946        let mut branch = state.branch().unwrap();
947        branch.samplers.pop();
948        let (completion, _) = MockCompletion::new(CompletionOutcome::Success);
949        branch.attach_submission_completion(completion).unwrap();
950        assert!(matches!(
951            state.commit_branch(branch),
952            Err(RealtimeGenerationTransactionError::SamplerCardinality {
953                expected: 2,
954                actual: 1
955            })
956        ));
957        assert_eq!(state.samplers().len(), 2);
958    }
959
960    #[test]
961    fn incomplete_decision_driver_does_not_change_branch_sampling_state() {
962        let state = state();
963        let mut branch = state.branch().unwrap();
964        let plan = SequentialDecisionPlan::new(
965            [PredictionDirective::Sample, PredictionDirective::Sample],
966            false,
967            false,
968        )
969        .unwrap();
970        let mut driver = branch
971            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
972            .unwrap();
973        driver
974            .resolve(0, &10, crate::TokenDomain::new(100), &())
975            .unwrap();
976        assert!(matches!(
977            branch.adopt_decision_driver(driver),
978            Err(SequentialDecisionError::Incomplete { .. })
979        ));
980        assert_eq!(branch.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
981        assert_eq!(branch.random_state(), Some(&7));
982    }
983
984    #[test]
985    fn invalid_decision_tokens_leave_canonical_realtime_state_unchanged() {
986        let state = state();
987        let mut branch = state.branch().unwrap();
988        branch.model_state_mut().model_step = 9;
989        branch.model_state_mut().cache_offset = 10;
990        branch
991            .schedule_state_mut()
992            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
993            .unwrap();
994        let plan = SequentialDecisionPlan::new(
995            [PredictionDirective::Sample, PredictionDirective::Force(100)],
996            false,
997            true,
998        )
999        .unwrap();
1000        let mut driver = branch
1001            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1002            .unwrap();
1003        assert!(matches!(
1004            driver.resolve(0, &100, crate::TokenDomain::new(100), &()),
1005            Err(SequentialDecisionError::Backend(_))
1006        ));
1007        assert!(driver.decisions().is_empty());
1008        drop(driver);
1009        drop(branch);
1010
1011        assert_eq!(state.model_state().model_step, 1);
1012        assert_eq!(state.model_state().cache_offset, 2);
1013        assert_eq!(state.schedule_state().frontier(), 0);
1014        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1015        assert_eq!(state.random_state(), Some(&7));
1016
1017        let mut branch = state.branch().unwrap();
1018        branch.model_state_mut().cache_offset = 11;
1019        branch
1020            .schedule_state_mut()
1021            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
1022            .unwrap();
1023        let plan = SequentialDecisionPlan::new(
1024            [
1025                PredictionDirective::Force(1),
1026                PredictionDirective::Force(100),
1027            ],
1028            false,
1029            true,
1030        )
1031        .unwrap();
1032        let driver = branch
1033            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1034            .unwrap();
1035        assert!(matches!(
1036            driver.forced_tail_tokens(0, 2, [crate::TokenDomain::new(100); 2], &()),
1037            Err(SequentialDecisionError::Backend(_))
1038        ));
1039        drop(driver);
1040        drop(branch);
1041        assert_eq!(state.model_state().cache_offset, 2);
1042        assert_eq!(state.schedule_state().frontier(), 0);
1043        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1044        assert_eq!(state.random_state(), Some(&7));
1045    }
1046
1047    #[test]
1048    fn duplicate_completion_evidence_is_rejected() {
1049        let state = state();
1050        let mut branch = state.branch().unwrap();
1051        branch
1052            .attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0)
1053            .unwrap();
1054        assert_eq!(
1055            branch.attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0),
1056            Err(RealtimeCompletionAttachmentError::AlreadyAttached)
1057        );
1058    }
1059}