Skip to main content

superfluid_engine/mock/
mod.rs

1//! `MockEngine`.
2
3mod ops;
4mod state;
5mod strategy;
6pub use strategy::Cert;
7mod tick;
8
9use std::collections::HashMap;
10use std::ffi::CStr;
11
12use superfluid_abi::{
13    array::read_array, encoding as enc, op_state, space_kind, ArenaBounds, MatchCandidate,
14    MatchResult, OpComplete, OpStatus, SpaceMatch, StateSpaceDesc,
15    StrategyGrant, StrategyRegistration, TickEvents, TickPlan,
16    LaneAdmit, TokenRange, RecordArena, Status,
17};
18
19use crate::engine::{Engine, EngineConfig, OpHandle, SeqHandle, SpaceConfig, StageOutput};
20use crate::rings::Rings;
21use state::{Busy, Cache, CacheEntry, Lane, OpKind, OpState, SeedLease, Sequence, SpaceState};
22use strategy::{default_certs, RegisteredStrategy, RegistrationCheck};
23
24struct AbiStore<T> {
25    _arena: RecordArena,
26    value: Box<T>,
27}
28
29const MOCK_PIPELINE_LAYERS: u32 = 24;
30
31type AdmitRefusal = (fn(&LaneAdmit) -> bool, Status);
32
33pub struct MockEngine {
34    cfg: EngineConfig,
35    bundle_fp: u64,
36    target_layers: u32,
37    target_arch: String,
38
39    descs: Vec<StateSpaceDesc>,
40    lanes: HashMap<u64, Lane>,
41    seqs: HashMap<SeqHandle, Sequence>,
42    seeds: HashMap<u64, SeedLease>,
43    media: HashMap<u64, u32>,
44    next_media: u64,
45    pending_media_binds: HashMap<u64, Vec<(u32, u64)>>,
46    ops: HashMap<OpHandle, OpState>,
47    completions_pending: Vec<OpComplete>,
48    cache: Cache,
49    strategies: Vec<RegisteredStrategy>,
50    refuse_admits: Option<AdmitRefusal>,
51    certified_combos: HashMap<String, ([u8; 32], [u8; 32])>,
52
53    next_seq: SeqHandle,
54    next_op: OpHandle,
55    next_seed: u64,
56    next_slot: u32,
57    next_cert: u32,
58    grammars: std::collections::HashSet<u32>,
59    next_grammar: u32,
60    tick_count: u64,
61    last_plan_seq: Option<u64>,
62    pool_used: u64,
63
64    grant_store: Option<AbiStore<StrategyGrant>>,
65    match_store: Option<AbiStore<MatchResult>>,
66    events_store: Option<AbiStore<TickEvents>>,
67
68    record: Option<String>,
69    pub(crate) force_finish: HashMap<u64, u32>,
70    pub(crate) inject_fault: HashMap<u64, u32>,
71    pub(crate) faulted: std::collections::HashSet<u64>,
72}
73
74impl MockEngine {
75    pub fn new(cfg: EngineConfig) -> MockEngine {
76        let descs = cfg.spaces.iter().map(build_desc).collect();
77        MockEngine {
78            cfg,
79            bundle_fp: 0xBA5E_0001,
80            target_layers: 32,
81            target_arch: "mock-arch".to_string(),
82            descs,
83            lanes: HashMap::new(),
84            seqs: HashMap::new(),
85            seeds: HashMap::new(),
86            media: HashMap::new(),
87            next_media: 1,
88            pending_media_binds: HashMap::new(),
89            ops: HashMap::new(),
90            completions_pending: Vec::new(),
91            cache: Cache::new(),
92            strategies: Vec::new(),
93            certified_combos: HashMap::new(),
94            next_seq: 1,
95            next_op: 1,
96            next_seed: 1,
97            next_slot: 1,
98            next_cert: 1,
99            grammars: std::collections::HashSet::new(),
100            next_grammar: 1,
101            tick_count: 0,
102            last_plan_seq: None,
103            pool_used: 0,
104            grant_store: None,
105            match_store: None,
106            events_store: None,
107            record: None,
108            force_finish: HashMap::new(),
109            inject_fault: HashMap::new(),
110            faulted: std::collections::HashSet::new(),
111            refuse_admits: None,
112        }
113    }
114
115    pub fn refusing_admits(mut self, refuse: fn(&LaneAdmit) -> bool, status: Status) -> MockEngine {
116        self.refuse_admits = Some((refuse, status));
117        self
118    }
119
120    pub fn with_capability_descriptor(mut self, record: impl Into<String>) -> MockEngine {
121        self.record = Some(record.into());
122        self
123    }
124
125    pub fn config(&self) -> &EngineConfig {
126        &self.cfg
127    }
128
129    pub fn bundle_fingerprint(&self) -> u64 {
130        self.bundle_fp
131    }
132
133    fn space_cfg(&self, space_id: u32) -> Option<&SpaceConfig> {
134        self.cfg.spaces.iter().find(|s| s.space_id == space_id)
135    }
136
137    fn space_ops_gate(&self, space_id: u32) -> Result<&SpaceConfig, Status> {
138        let cfg = self.space_cfg(space_id).ok_or(Status::UnknownHandle)?;
139        if cfg.flags & superfluid_abi::space_flag::OPS_UNAVAILABLE != 0 {
140            return Err(Status::Unsupported);
141        }
142        Ok(cfg)
143    }
144
145    fn has_unservable_space(&self) -> bool {
146        self.cfg
147            .spaces
148            .iter()
149            .any(|s| s.flags & superfluid_abi::space_flag::OPS_UNAVAILABLE != 0)
150    }
151
152    pub fn lane_sequence(&self, lane_tag: u64) -> Option<SeqHandle> {
153        self.lanes.get(&lane_tag).map(|l| l.seq)
154    }
155
156    pub fn lane_committed(&self, lane_tag: u64) -> Option<&[u32]> {
157        self.lanes.get(&lane_tag).map(|l| l.committed.as_slice())
158    }
159
160    pub fn create_sequence(&mut self) -> SeqHandle {
161        self.alloc_seq()
162    }
163
164    pub fn space_valid_len(&self, seq: SeqHandle, space_id: u32) -> Option<u64> {
165        self.seqs
166            .get(&seq)
167            .and_then(|q| q.spaces.get(&space_id))
168            .map(|s| s.valid_len)
169    }
170
171    pub fn space_taint(&self, seq: SeqHandle, space_id: u32) -> Option<u32> {
172        self.seqs
173            .get(&seq)
174            .and_then(|q| q.spaces.get(&space_id))
175            .map(|s| s.taint_bits)
176    }
177
178    pub fn pool_used(&self) -> u64 {
179        self.pool_used
180    }
181
182    pub fn force_finish(&mut self, lane_tag: u64, finish: u32) {
183        self.force_finish.insert(lane_tag, finish);
184    }
185
186    pub fn inject_fault(&mut self, lane_tag: u64, code: u32) {
187        self.inject_fault.insert(lane_tag, code);
188    }
189
190    pub fn grant_certs_for_test(&mut self, strategy_id: &str, certs: Vec<strategy::Cert>) {
191        if let Some(s) = self
192            .strategies
193            .iter_mut()
194            .find(|s| s.strategy_id == strategy_id)
195        {
196            s.certs = certs;
197        }
198    }
199
200    #[allow(clippy::too_many_arguments)]
201    pub fn make_cert(
202        &mut self,
203        exactness: u8,
204        sampling_modes: u8,
205        param_domain: u32,
206        grammar_allowed: bool,
207        admissible_host_identities: Vec<[u8; 32]>,
208    ) -> strategy::Cert {
209        let id = self.next_cert;
210        self.next_cert += 1;
211        Cert {
212            cert_id: id,
213            exactness,
214            sampling_modes,
215            grammar_allowed,
216            logit_bias_allowed: false,
217            param_domain,
218            verification_shape_class: 1,
219            admissible_host_identities,
220        }
221    }
222
223    pub(crate) fn alloc_seq(&mut self) -> SeqHandle {
224        let h = self.next_seq;
225        self.next_seq += 1;
226        let spaces = self
227            .cfg
228            .spaces
229            .iter()
230            .map(|s| (s.space_id, SpaceState::new(s)))
231            .collect();
232        self.seqs.insert(
233            h,
234            Sequence {
235                spaces,
236                busy: Busy::Idle,
237            },
238        );
239        h
240    }
241
242    pub(crate) fn paged_block_bytes(&self) -> u64 {
243        let page = self
244            .cfg
245            .spaces
246            .iter()
247            .find(|s| s.page_size_tokens > 0)
248            .map(|s| s.page_size_tokens as u64)
249            .unwrap_or(1);
250        self.paged_bytes_per_token() * page
251    }
252
253    pub(crate) fn paged_bytes_per_token(&self) -> u64 {
254        self.cfg
255            .spaces
256            .iter()
257            .filter(|s| {
258                s.kind == space_kind::PAGED_TOKEN_KV || s.kind == space_kind::DEPTH_PAGED_KV
259            })
260            .map(|s| s.bytes_per_token)
261            .sum()
262    }
263
264    pub(crate) fn sweep_expired_seeds(&mut self) {
265        let now = self.tick_count;
266        let expired: Vec<u64> = self
267            .seeds
268            .values()
269            .filter(|l| l.expires_at_tick < now)
270            .map(|l| l.handle)
271            .collect();
272        for h in expired {
273            if let Some(lease) = self.seeds.remove(&h) {
274                for (_, id) in lease.pinned_entries {
275                    if let Some(e) = self.cache.get_mut(id) {
276                        e.pinned = e.pinned.saturating_sub(1);
277                    }
278                }
279            }
280        }
281    }
282
283    pub(crate) fn release_seq_pool(&mut self, seq: &Sequence) {
284        for (space_id, s) in &seq.spaces {
285            if let Some(c) = self.cfg.spaces.iter().find(|c| c.space_id == *space_id) {
286                if c.bytes_per_token > 0 {
287                    let demoted: u64 = s.demoted.iter().map(|(a, b, _)| b - a).sum();
288                    let resident = s.valid_len.saturating_sub(demoted);
289                    self.pool_used = self.pool_used.saturating_sub(resident * c.bytes_per_token);
290                }
291            }
292        }
293    }
294
295    pub(crate) fn evict_within_envelope(
296        &mut self,
297        needed: u64,
298        evictable_classes: u64,
299        max_evict_bytes: u64,
300        protected_quota_bytes: u64,
301    ) -> Vec<(u64, u64)> {
302        let mut evicted = Vec::new();
303        let mut freed = 0u64;
304        let total: u64 = self.cache.entries.iter().map(|e| e.bytes).sum();
305        let cap = max_evict_bytes.min(total.saturating_sub(protected_quota_bytes));
306        while self.pool_used + needed > self.cfg.pool_bytes && freed < cap {
307            let victim = self
308                .cache
309                .entries
310                .iter()
311                .filter(|e| e.pinned == 0 && e.cache_class & evictable_classes != 0)
312                .min_by_key(|e| e.lru)
313                .map(|e| (e.id, e.bytes, e.space_id));
314            let Some((id, bytes, space_id)) = victim else {
315                break;
316            };
317            self.cache.entries.retain(|e| e.id != id);
318            let paged = self
319                .space_cfg(space_id)
320                .map(|c| c.bytes_per_token > 0)
321                .unwrap_or(false);
322            if paged {
323                self.pool_used = self.pool_used.saturating_sub(bytes);
324            }
325            freed += bytes;
326            evicted.push((id, bytes));
327        }
328        evicted
329    }
330}
331
332fn build_desc(s: &SpaceConfig) -> StateSpaceDesc {
333    let mut name = [0u8; 32];
334    let n = s.name.as_bytes();
335    name[..n.len().min(31)].copy_from_slice(&n[..n.len().min(31)]);
336    let paged = s.kind == space_kind::PAGED_TOKEN_KV || s.kind == space_kind::DEPTH_PAGED_KV;
337    StateSpaceDesc {
338        space_id: s.space_id,
339        kind: s.kind,
340        version_tag: s.version_tag,
341        bytes_per_token: s.bytes_per_token,
342        blob_bytes: s.blob_bytes,
343        page_size_tokens: s.page_size_tokens,
344        fork_cost_class: if paged { 1 } else { 2 },
345        fork_cost_bytes: if paged { 0 } else { s.blob_bytes },
346        snapshot_cadence: s.snapshot_cadence,
347        snapshot_interval_tokens: s.snapshot_interval_tokens,
348        placement: 0,
349        flags: s.flags,
350        name,
351    }
352}
353
354impl Engine for MockEngine {
355    fn capability_descriptor(&self) -> Option<String> {
356        if let Some(r) = &self.record {
357            return Some(r.clone());
358        }
359        Some(
360            r#"{"descriptor_version":1,"architecture":"mock","backend":"mock","workload":{"causal_generation":true,"embedding":true,"speech_to_text":true},"modalities":{"image_encode":true,"gemma_audio_encode":true,"whisper_transcribe":true,"whisper_translate":true},"state":{"recurrent_snapshot":"no recurrent state in this architecture"}}"#
361                .to_string(),
362        )
363    }
364
365    fn embed(&mut self, tokens: &[u32]) -> Result<Vec<f32>, Status> {
366        let mut v = [0.0f32; 8];
367        for (i, &t) in tokens.iter().enumerate() {
368            v[i % 8] += ((t % 97) as f32 + 1.0) / (i as f32 + 1.0);
369        }
370        let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-6);
371        Ok(v.iter().map(|x| x / norm).collect())
372    }
373
374    fn logit_bias_create(&mut self, tokens: &[i32], values: &[f32]) -> Result<u32, Status> {
375        if tokens.is_empty() || values.is_empty() {
376            return Err(Status::Unsupported);
377        }
378        Ok(1)
379    }
380
381    fn logit_bias_free(&mut self, _handle: u32) -> Result<(), Status> {
382        Ok(())
383    }
384
385    fn transcribe(
386        &mut self,
387        _audio_path: &str,
388        params: &crate::engine::TranscribeParams,
389        on_segment: Option<crate::engine::SegmentSink<'_>>,
390    ) -> Result<crate::engine::Transcription, Status> {
391        let base = if params.translate { "mock translation" } else { "mock transcription" };
392        let text = match params.prompt.as_deref().filter(|p| !p.is_empty()) {
393            Some(p) => format!("{p} {base}"),
394            None => base.to_string(),
395        };
396        let language = params
397            .language
398            .as_deref()
399            .filter(|l| *l != "auto")
400            .unwrap_or("en")
401            .to_string();
402        let seg = |start_ms: i32, end_ms: i32, text: String| crate::engine::TranscriptSegment {
403            start_ms,
404            end_ms,
405            text,
406            avg_logprob: -0.25,
407            no_speech_prob: 0.01,
408            compression_ratio: 1.5,
409            temperature: 0.0,
410        };
411        let Some(cb) = on_segment else {
412            return Ok(crate::engine::Transcription {
413                text: text.clone(),
414                language,
415                duration_ms: 1000,
416                segments: vec![seg(0, 1000, text)],
417            });
418        };
419        let mut segments = Vec::new();
420        let words: Vec<&str> = text.split_whitespace().collect();
421        let n = words.len().max(1) as i32;
422        for (i, w) in words.iter().enumerate() {
423            std::thread::sleep(std::time::Duration::from_millis(2));
424            let (start, end) = (i as i32 * 1000 / n, (i as i32 + 1) * 1000 / n);
425            segments.push(seg(start, end, (*w).to_string()));
426            if !cb(start, end, w) {
427                break;
428            }
429        }
430        Ok(crate::engine::Transcription {
431            text: segments.iter().map(|s| s.text.as_str()).collect::<Vec<_>>().join(" "),
432            language,
433            duration_ms: 1000,
434            segments,
435        })
436    }
437
438    fn lora_load(&mut self, _adapter_path: &str) -> Result<(), Status> {
439        Ok(())
440    }
441    fn lora_unload(&mut self) -> Result<(), Status> {
442        Ok(())
443    }
444
445    fn tick<'a>(
446        &'a mut self,
447        plan: &TickPlan,
448        arena: &RecordArena,
449        rings: &mut dyn Rings,
450    ) -> Result<&'a TickEvents, Status> {
451        if !self.cfg.tick_delay.is_zero() {
452            std::thread::sleep(self.cfg.tick_delay);
453        }
454        self.run_tick(plan, arena, rings)?;
455        Ok(&self
456            .events_store
457            .as_ref()
458            .expect("run_tick stores events")
459            .value)
460    }
461
462    fn pump(&mut self) {
463        let handles: Vec<OpHandle> = self
464            .ops
465            .values()
466            .filter(|o| o.state == op_state::RUNNING)
467            .map(|o| o.handle)
468            .collect();
469        for h in handles {
470            self.complete_op(h);
471        }
472    }
473
474    fn state_spaces(&self) -> &[StateSpaceDesc] {
475        &self.descs
476    }
477
478    fn grammar_create(&mut self, json_schema: &str) -> Result<u32, Status> {
479        let t = json_schema.trim();
480        let balanced = {
481            let mut depth = 0i32;
482            let mut in_str = false;
483            let mut esc = false;
484            for c in t.chars() {
485                match c {
486                    _ if esc => esc = false,
487                    '\\' if in_str => esc = true,
488                    '"' => in_str = !in_str,
489                    '{' if !in_str => depth += 1,
490                    '}' if !in_str => depth -= 1,
491                    _ => {}
492                }
493                if depth < 0 {
494                    break;
495                }
496            }
497            depth == 0 && !in_str
498        };
499        if !t.starts_with('{') || !t.ends_with('}') || !balanced {
500            return Err(Status::RejectBadStruct);
501        }
502        let h = self.next_grammar;
503        self.next_grammar += 1;
504        self.grammars.insert(h);
505        Ok(h)
506    }
507
508    fn grammar_create_structural(&mut self, tag_json: &str) -> Result<u32, Status> {
509        self.grammar_create(tag_json)
510    }
511
512    fn grammar_free(&mut self, handle: u32) -> Result<(), Status> {
513        if self.grammars.remove(&handle) {
514            Ok(())
515        } else {
516            Err(Status::UnknownHandle)
517        }
518    }
519
520    fn strategy_register(
521        &mut self,
522        reg: &StrategyRegistration,
523    ) -> Result<&StrategyGrant, Status> {
524        let bounds = ArenaBounds {
525            base: 0,
526            len: usize::MAX,
527        };
528        // SAFETY: in-process registration call; the worker materialized
529        // these arrays and they outlive the call.
530        let capabilities = unsafe { read_array(&reg.capabilities, &bounds) }
531            .map_err(|e| e.status())?
532            .collect();
533        // SAFETY: as above.
534        let taps = unsafe { read_array(&reg.taps, &bounds) }
535            .map_err(|e| e.status())?
536            .collect();
537        // SAFETY: as above. Artifacts are decoded fail-closed like every
538        // other registration array; a null or empty load path is refused.
539        let artifacts: Vec<superfluid_abi::Artifact> =
540            unsafe { read_array(&reg.artifacts, &bounds) }
541                .map_err(|e| e.status())?
542                .collect();
543        for a in &artifacts {
544            if a.load_path.is_null() {
545                return Err(Status::RegistrationRefused);
546            }
547        }
548        // SAFETY: as above; target_archs is an array of C-string pointers.
549        let arch_ptrs: Vec<u64> = unsafe { read_array(&reg.target_archs, &bounds) }
550            .map_err(|e| e.status())?
551            .collect();
552        let target_archs: Vec<String> = arch_ptrs
553            .into_iter()
554            .map(|p| {
555                if p == 0 {
556                    return Err(Status::RegistrationRefused);
557                }
558                // SAFETY: registration contract: NUL-terminated strings.
559                Ok(unsafe { CStr::from_ptr(p as *const std::ffi::c_char) }
560                    .to_string_lossy()
561                    .into_owned())
562            })
563            .collect::<Result<_, _>>()?;
564
565        if reg.strategy_id.is_null() {
566            return Err(Status::RegistrationRefused);
567        }
568        // SAFETY: checked non-null; registration contract: NUL-terminated.
569        let strategy_id = unsafe { CStr::from_ptr(reg.strategy_id) }
570            .to_string_lossy()
571            .into_owned();
572
573        let check = RegistrationCheck {
574            reg,
575            capabilities,
576            taps,
577            target_archs,
578        };
579        let host_compatible = check.validate(self.target_layers, &self.target_arch)?;
580        if reg.kernel_caps_required & !self.cfg.kernel_caps != 0 {
581            return Err(Status::RegistrationRefused);
582        }
583
584        let slot = self.next_slot;
585        self.next_slot += 1;
586        let certs = match self.certified_combos.get(&strategy_id) {
587            None => {
588                let certs = default_certs(&strategy_id, &mut self.next_cert);
589                if !certs.is_empty() {
590                    self.certified_combos
591                        .insert(strategy_id.clone(), (reg.impl_hash, reg.config_hash));
592                }
593                certs
594            }
595            Some((ih, ch)) if *ih == reg.impl_hash && *ch == reg.config_hash => {
596                default_certs(&strategy_id, &mut self.next_cert)
597            }
598            Some(_) => Vec::new(),
599        };
600        let registered = RegisteredStrategy {
601            slot,
602            strategy_id,
603            impl_hash: reg.impl_hash,
604            config_hash: reg.config_hash,
605            host_compatible,
606            certs,
607            reserved_bytes: reg.est_state_bytes,
608        };
609
610        let mut arena = RecordArena::new();
611        let cert_records: Vec<_> = registered
612            .certs
613            .iter()
614            .map(|c| c.to_abi(&mut arena))
615            .collect();
616        let certificates = arena.push_records(&cert_records);
617        let grant = StrategyGrant {
618            struct_size: std::mem::size_of::<StrategyGrant>() as u64,
619            strategy_slot: slot,
620            _pad0: 0,
621            certificates,
622            state_spaces: superfluid_abi::Array::EMPTY,
623            reserved_bytes: registered.reserved_bytes,
624        };
625        self.strategies.push(registered);
626        self.grant_store = Some(AbiStore {
627            _arena: arena,
628            value: Box::new(grant),
629        });
630        Ok(&self.grant_store.as_ref().expect("just stored").value)
631    }
632
633    fn space_match(
634        &mut self,
635        space_id: u32,
636        span_tokens: &[u32],
637        _media_deps: &[[u8; 32]],
638    ) -> Result<&MatchResult, Status> {
639        let mut arena = RecordArena::new();
640        let mut space_matches = Vec::new();
641        for s in &self.cfg.spaces {
642            if space_id != superfluid_abi::ALL_SPACES && s.space_id != space_id {
643                continue;
644            }
645            let mut candidates: Vec<MatchCandidate> = Vec::new();
646            for e in self
647                .cache
648                .entries
649                .iter()
650                .filter(|e| e.space_id == s.space_id)
651            {
652                let lcp = longest_common_prefix(&e.tokens, span_tokens);
653                let candidate_len = match s.kind {
654                    space_kind::PAGED_TOKEN_KV | space_kind::DEPTH_PAGED_KV => {
655                        let page = s.page_size_tokens.max(1) as u64;
656                        (lcp / page) * page
657                    }
658                    space_kind::RING_KV | space_kind::RECURRENT_BLOB => {
659                        *e.boundaries
660                            .iter()
661                            .filter(|b| **b <= lcp)
662                            .max()
663                            .unwrap_or(&0)
664                    }
665                    _ => 0,
666                };
667                if candidate_len > 0 {
668                    candidates.push(MatchCandidate {
669                        prefix_len: candidate_len,
670                        provenance_digest: e.provenance,
671                        taint_bits: e.taint_bits,
672                        resident_tier: e.tier,
673                        _pad0: [0; 3],
674                    });
675                }
676            }
677            space_matches.push((s.space_id, candidates));
678        }
679        if space_matches.is_empty() {
680            return Err(Status::UnknownHandle);
681        }
682        let records: Vec<SpaceMatch> = space_matches
683            .into_iter()
684            .map(|(id, cands)| SpaceMatch {
685                space_id: id,
686                _pad0: 0,
687                candidates: arena.push_records(&cands),
688            })
689            .collect();
690        let result = MatchResult {
691            struct_size: std::mem::size_of::<MatchResult>() as u64,
692            spaces: arena.push_records(&records),
693        };
694        self.match_store = Some(AbiStore {
695            _arena: arena,
696            value: Box::new(result),
697        });
698        Ok(&self.match_store.as_ref().expect("just stored").value)
699    }
700
701    fn seed_acquire(
702        &mut self,
703        span_tokens: &[u32],
704        prefix_len: u64,
705        determinism_class: u8,
706    ) -> Result<u64, Status> {
707        if prefix_len == 0 || prefix_len as usize > span_tokens.len() {
708            return Err(Status::RejectBounds);
709        }
710        self.sweep_expired_seeds();
711        if self.has_unservable_space() {
712            return Err(Status::SeedUnservable);
713        }
714        if self
715            .cfg
716            .spaces
717            .iter()
718            .any(|s| s.flags & superfluid_abi::space_flag::NO_PREFIX_CACHE != 0)
719        {
720            return Err(Status::SeedUnservable);
721        }
722        let prefix = &span_tokens[..prefix_len as usize];
723        let deterministic = determinism_class == superfluid_abi::determinism::DETERMINISTIC;
724
725        let mut pinned = Vec::new();
726        for s in &self.cfg.spaces {
727            if s.kind == space_kind::ENCODER_CACHE {
728                continue;
729            }
730            let page = s.page_size_tokens.max(1) as u64;
731            let found = self.cache.entries.iter().find(|e| {
732                if e.space_id != s.space_id || e.tokens.len() < prefix.len() {
733                    return false;
734                }
735                if e.tokens[..prefix.len()] != *prefix {
736                    return false;
737                }
738                if deterministic && e.taint_bits != 0 {
739                    return false;
740                }
741                match s.kind {
742                    space_kind::PAGED_TOKEN_KV | space_kind::DEPTH_PAGED_KV => {
743                        prefix_len.is_multiple_of(page)
744                    }
745                    _ => e.boundaries.contains(&prefix_len),
746                }
747            });
748            match found {
749                Some(e) => pinned.push((s.space_id, e.id)),
750                None => {
751                    return Err(Status::SeedUnservable);
752                }
753            }
754        }
755        for (_, id) in &pinned {
756            if let Some(e) = self.cache.get_mut(*id) {
757                e.pinned += 1;
758            }
759            self.cache.touch(*id);
760        }
761        let handle = self.next_seed;
762        self.next_seed += 1;
763        self.seeds.insert(
764            handle,
765            SeedLease {
766                handle,
767                prefix: prefix.to_vec(),
768                determinism: determinism_class,
769                pinned_entries: pinned,
770                expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
771                adopted: None,
772            },
773        );
774        Ok(handle)
775    }
776
777    fn media_probe(&mut self, _image_path: &str) -> Result<superfluid_abi::MediaInfo, Status> {
778        if self.cfg.image_token_id == 0 {
779            return Err(Status::Unsupported);
780        }
781        Ok(superfluid_abi::MediaInfo {
782            n_tokens: self.cfg.media_tokens_per_image,
783            image_token_id: self.cfg.image_token_id,
784            boi_token_id: 0,
785            eoi_token_id: 0,
786            preprocess_fp: 0x4d4f434b,
787        })
788    }
789
790    fn media_encode(&mut self, image_path: &str) -> Result<(u64, superfluid_abi::MediaInfo), Status> {
791        let info = self.media_probe(image_path)?;
792        let h = self.next_media;
793        self.next_media += 1;
794        self.media.insert(h, info.n_tokens);
795        Ok((h, info))
796    }
797
798    fn media_release(&mut self, media_handle: u64) -> Result<(), Status> {
799        self.media.remove(&media_handle).map(|_| ()).ok_or(Status::UnknownHandle)
800    }
801
802    fn media_bind(&mut self, lane_tag: u64, media_handle: u64, token_offset: u32) -> Result<(), Status> {
803        if !self.media.contains_key(&media_handle) {
804            return Err(Status::UnknownHandle);
805        }
806        let binds = match self.lanes.get_mut(&lane_tag) {
807            Some(l) => &mut l.media_binds,
808            None => self.pending_media_binds.entry(lane_tag).or_default(),
809        };
810        binds.push((token_offset, media_handle));
811        binds.sort();
812        Ok(())
813    }
814
815    fn seed_adopt(&mut self, seq: SeqHandle, span_tokens: &[u32]) -> Result<u64, Status> {
816        let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
817        if s.any_op_active() || s.busy == Busy::Ticking {
818            return Err(Status::Busy);
819        }
820        if self.lanes.values().any(|l| l.seq == seq) {
821            return Err(Status::UnknownHandle);
822        }
823        let len = span_tokens.len() as u64;
824        if len == 0
825            || s
826                .spaces
827                .iter()
828                .any(|(_, sp)| sp.kind != space_kind::ENCODER_CACHE && sp.valid_len != len)
829        {
830            return Err(Status::SeedUnservable);
831        }
832        self.sweep_expired_seeds();
833        let handle = self.next_seed;
834        self.next_seed += 1;
835        self.seeds.insert(
836            handle,
837            SeedLease {
838                handle,
839                prefix: span_tokens.to_vec(),
840                determinism: 0,
841                pinned_entries: Vec::new(),
842                expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
843                adopted: Some(seq),
844            },
845        );
846        Ok(handle)
847    }
848
849    fn seed_lease_ticks(&self) -> Option<u64> {
850        Some(self.cfg.seed_ttl_ticks)
851    }
852
853    fn seed_release(&mut self, seed_handle: u64) -> Result<(), Status> {
854        let lease = self
855            .seeds
856            .remove(&seed_handle)
857            .ok_or(Status::UnknownHandle)?;
858        for (_, id) in lease.pinned_entries {
859            if let Some(e) = self.cache.get_mut(id) {
860                e.pinned = e.pinned.saturating_sub(1);
861            }
862        }
863        Ok(())
864    }
865
866    fn seq_fork(&mut self, parent: SeqHandle, _flags: u32) -> Result<SeqHandle, Status> {
867        let p = self.seqs.get(&parent).ok_or(Status::UnknownHandle)?;
868        if p.any_op_active() || p.busy == Busy::Ticking {
869            return Err(Status::Busy);
870        }
871        let clone = Sequence {
872            spaces: p.spaces.clone(),
873            busy: Busy::Idle,
874        };
875        let h = self.next_seq;
876        self.next_seq += 1;
877        self.seqs.insert(h, clone);
878        Ok(h)
879    }
880
881    fn lane_sequence(&self, lane_tag: u64) -> Option<SeqHandle> {
882        MockEngine::lane_sequence(self, lane_tag)
883    }
884
885    fn create_sequence(&mut self) -> Result<SeqHandle, Status> {
886        Ok(self.alloc_seq())
887    }
888
889    fn free_sequence(&mut self, seq: SeqHandle) -> Result<(), Status> {
890        let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
891        if s.any_op_active() || s.busy == Busy::Ticking {
892            return Err(Status::Busy);
893        }
894        let s = self.seqs.remove(&seq).expect("checked above");
895        self.release_seq_pool(&s);
896        Ok(())
897    }
898
899    fn publish_sequence(&mut self, seq: SeqHandle, tokens: &[u32]) -> Result<(), Status> {
900        let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
901        if s.any_op_active() || s.busy == Busy::Ticking {
902            return Err(Status::Busy);
903        }
904        let over_claim = s.spaces.iter().any(|(id, sp)| {
905            sp.valid_len < tokens.len() as u64
906                && self
907                    .cfg
908                    .spaces
909                    .iter()
910                    .any(|c| c.space_id == *id && c.bytes_per_token > 0)
911        });
912        if over_claim {
913            return Err(Status::OutOfBoundary);
914        }
915        let entries: Vec<CacheEntry> = s
916            .spaces
917            .iter()
918            .filter(|(_, sp)| sp.kind != space_kind::ENCODER_CACHE)
919            .filter_map(|(space_id, sp)| {
920                let len = sp.valid_len.min(tokens.len() as u64);
921                if len == 0 {
922                    return None;
923                }
924                let cfg = self.cfg.spaces.iter().find(|c| c.space_id == *space_id)?;
925                let bytes = if cfg.blob_bytes > 0 {
926                    cfg.blob_bytes
927                } else {
928                    len * cfg.bytes_per_token
929                };
930                Some(CacheEntry {
931                    id: 0,
932                    space_id: *space_id,
933                    tokens: tokens[..len as usize].to_vec(),
934                    provenance: sp.provenance,
935                    taint_bits: sp.taint_bits,
936                    tier: superfluid_abi::tier::GPU,
937                    bytes,
938                    cache_class: 1,
939                    pinned: 0,
940                    boundaries: sp.boundaries.clone(),
941                    lru: 0,
942                })
943            })
944            .collect();
945        let mut released: u64 = 0;
946        for e in entries {
947            let space_id = e.space_id;
948            let bytes = e.bytes;
949            let paged = self
950                .space_cfg(space_id)
951                .map(|c| c.bytes_per_token > 0)
952                .unwrap_or(false);
953            let outcome = self.cache.publish(e);
954            if paged && outcome.stored.is_none() {
955                released += bytes;
956            }
957            for (victim_space, victim_bytes) in outcome.evicted {
958                if self
959                    .space_cfg(victim_space)
960                    .map(|c| c.bytes_per_token > 0)
961                    .unwrap_or(false)
962                {
963                    released += victim_bytes;
964                }
965            }
966        }
967        self.seqs.remove(&seq);
968        self.pool_used = self.pool_used.saturating_sub(released);
969        Ok(())
970    }
971
972    fn space_export_size(
973        &mut self,
974        seq: SeqHandle,
975        space_id: u32,
976        range: TokenRange,
977        encoding: u8,
978    ) -> Result<(u64, u64), Status> {
979        let cfg = self.space_ops_gate(space_id)?;
980        let (bpt, blob) = (cfg.bytes_per_token, cfg.blob_bytes);
981        let s = self
982            .seqs
983            .get(&seq)
984            .and_then(|q| q.spaces.get(&space_id))
985            .ok_or(Status::UnknownHandle)?;
986        let bytes = ops::export_size(s, bpt, blob, range, encoding)?;
987        Ok((bytes, s.content_gen))
988    }
989
990    fn space_snapshot(
991        &mut self,
992        seq: SeqHandle,
993        space_id: u32,
994        boundary_pos: u64,
995        dst_len: u64,
996        sizing_gen: u64,
997    ) -> Result<OpHandle, Status> {
998        self.submit_export(
999            seq,
1000            space_id,
1001            TokenRange {
1002                start: 0,
1003                end: boundary_pos,
1004            },
1005            enc::LOSSLESS,
1006            dst_len,
1007            sizing_gen,
1008            OpKind::Snapshot,
1009        )
1010    }
1011
1012    fn space_snapshot_boundary(
1013        &mut self,
1014        seq: SeqHandle,
1015        space_id: u32,
1016        cap: u64,
1017    ) -> Result<u64, Status> {
1018        let cfg = self.space_ops_gate(space_id)?;
1019        let (cadence, interval) = (cfg.snapshot_cadence, cfg.snapshot_interval_tokens);
1020        let q = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
1021        let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1022        let len = cap.min(s.valid_len);
1023        if s.kind != superfluid_abi::space_kind::PAGED_TOKEN_KV {
1024            if let Some(&b) = s.boundaries.iter().filter(|&&b| b <= len).max() {
1025                return Ok(b);
1026            }
1027            if cadence == 2 && interval > 0 {
1028                return Ok(len / interval as u64 * interval as u64);
1029            }
1030            return Ok(0);
1031        }
1032        Ok(len)
1033    }
1034
1035    fn space_restore(
1036        &mut self,
1037        seq: SeqHandle,
1038        space_id: u32,
1039        src: &[u8],
1040    ) -> Result<OpHandle, Status> {
1041        self.submit_import(seq, space_id, src, OpKind::Restore)
1042    }
1043
1044    fn space_trim(&mut self, seq: SeqHandle, space_id: u32, new_len: u64) -> Result<(), Status> {
1045        let interval = self.space_ops_gate(space_id)?.snapshot_interval_tokens;
1046        let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1047        if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1048            return Err(Status::Busy);
1049        }
1050        let s = q.spaces.get_mut(&space_id).ok_or(Status::UnknownHandle)?;
1051        if s.kind == space_kind::ENCODER_CACHE {
1052            return Err(Status::Unsupported);
1053        }
1054        if new_len > s.valid_len {
1055            return Err(Status::RejectBounds);
1056        }
1057        if s.is_blob_like() && !s.boundaries.contains(&new_len) {
1058            return Err(Status::OutOfBoundary);
1059        }
1060        s.valid_len = new_len;
1061        s.boundaries.retain(|b| *b <= new_len);
1062        s.content_gen += 1;
1063        let _ = interval;
1064        Ok(())
1065    }
1066
1067    fn space_demote(
1068        &mut self,
1069        seq: SeqHandle,
1070        space_id: u32,
1071        range: TokenRange,
1072        encoding: u8,
1073        dst_len: u64,
1074        sizing_gen: u64,
1075    ) -> Result<OpHandle, Status> {
1076        self.submit_export(
1077            seq,
1078            space_id,
1079            range,
1080            encoding,
1081            dst_len,
1082            sizing_gen,
1083            OpKind::Demote,
1084        )
1085    }
1086
1087    fn space_promote(
1088        &mut self,
1089        seq: SeqHandle,
1090        space_id: u32,
1091        _range: TokenRange,
1092        src: &[u8],
1093    ) -> Result<OpHandle, Status> {
1094        self.submit_import(seq, space_id, src, OpKind::Promote)
1095    }
1096
1097    fn op_poll(&mut self, op: OpHandle) -> Result<OpStatus, Status> {
1098        let o = self.ops.get(&op).ok_or(Status::UnknownHandle)?;
1099        Ok(OpStatus {
1100            struct_size: std::mem::size_of::<OpStatus>() as u64,
1101            state: o.state,
1102            _pad0: [0; 3],
1103            error: o.error,
1104            bytes_moved: o.bytes_moved,
1105        })
1106    }
1107
1108    fn take_op_output(&mut self, op: OpHandle) -> Option<Vec<u8>> {
1109        let o = self.ops.get_mut(&op)?;
1110        if o.state != op_state::DONE {
1111            return None;
1112        }
1113        o.output.take()
1114    }
1115
1116    fn n_layers(&self) -> u32 {
1117        MOCK_PIPELINE_LAYERS
1118    }
1119
1120    fn forward_stage(
1121        &mut self,
1122        tokens: &[u32],
1123        start_layer: u32,
1124        end_layer: u32,
1125        hidden_in: Option<Vec<u8>>,
1126    ) -> Result<StageOutput, Status> {
1127        let n = MOCK_PIPELINE_LAYERS;
1128        if start_layer > end_layer || end_layer > n {
1129            return Err(Status::RejectBounds);
1130        }
1131        let mut h: u64 = match hidden_in {
1132            None => {
1133                let mut b = Vec::with_capacity(tokens.len() * 4);
1134                for t in tokens {
1135                    b.extend_from_slice(&t.to_le_bytes());
1136                }
1137                xxhash_rust::xxh3::xxh3_64(&b)
1138            }
1139            Some(bytes) => {
1140                if bytes.len() != 8 {
1141                    return Err(Status::RejectBadStruct);
1142                }
1143                let mut arr = [0u8; 8];
1144                arr.copy_from_slice(&bytes);
1145                u64::from_le_bytes(arr)
1146            }
1147        };
1148        for l in start_layer..end_layer {
1149            let mut b = [0u8; 12];
1150            b[..8].copy_from_slice(&h.to_le_bytes());
1151            b[8..12].copy_from_slice(&l.to_le_bytes());
1152            h = xxhash_rust::xxh3::xxh3_64(&b);
1153        }
1154        if end_layer >= n {
1155            Ok(StageOutput::Token((h % self.cfg.vocab.max(1) as u64) as u32))
1156        } else {
1157            Ok(StageOutput::Hidden(h.to_le_bytes().to_vec()))
1158        }
1159    }
1160
1161    fn op_cancel(&mut self, op: OpHandle) -> Result<(), Status> {
1162        let o = self.ops.get_mut(&op).ok_or(Status::UnknownHandle)?;
1163        match o.state {
1164            op_state::PENDING | op_state::RUNNING => {
1165                o.state = op_state::CANCELLED;
1166                o.output = None;
1167                o.staged = None;
1168                let (seq, space) = (o.seq, o.space_id);
1169                let complete = OpComplete {
1170                    op,
1171                    state: op_state::CANCELLED,
1172                    _pad0: [0; 3],
1173                    error: 0,
1174                    bytes_moved: o.bytes_moved,
1175                };
1176                self.release_op_claim(seq, space);
1177                self.completions_pending.push(complete);
1178                Ok(())
1179            }
1180            _ => Ok(()),
1181        }
1182    }
1183
1184    fn cache_evict(
1185        &mut self,
1186        evictable_cache_classes: u64,
1187        protected_quota_bytes: u64,
1188        max_evict_bytes: u64,
1189        bytes_target: u64,
1190    ) -> Result<u64, Status> {
1191        let mut freed = 0u64;
1192        let total: u64 = self.cache.entries.iter().map(|e| e.bytes).sum();
1193        let cap = bytes_target
1194            .min(max_evict_bytes)
1195            .min(total.saturating_sub(protected_quota_bytes));
1196        while freed < cap {
1197            let victim = self
1198                .cache
1199                .entries
1200                .iter()
1201                .filter(|e| {
1202                    e.pinned == 0
1203                        && e.cache_class & evictable_cache_classes != 0
1204                        && freed + e.bytes <= cap
1205                })
1206                .min_by_key(|e| e.lru)
1207                .map(|e| (e.id, e.bytes, e.space_id));
1208            let Some((id, bytes, space_id)) = victim else {
1209                break;
1210            };
1211            self.cache.entries.retain(|e| e.id != id);
1212            if self
1213                .space_cfg(space_id)
1214                .map(|c| c.bytes_per_token > 0)
1215                .unwrap_or(false)
1216            {
1217                self.pool_used = self.pool_used.saturating_sub(bytes);
1218            }
1219            freed += bytes;
1220        }
1221        Ok(freed)
1222    }
1223}
1224
1225impl MockEngine {
1226    #[allow(clippy::too_many_arguments)]
1227    fn submit_export(
1228        &mut self,
1229        seq: SeqHandle,
1230        space_id: u32,
1231        range: TokenRange,
1232        encoding: u8,
1233        dst_len: u64,
1234        sizing_gen: u64,
1235        kind: OpKind,
1236    ) -> Result<OpHandle, Status> {
1237        let cfg = self.space_ops_gate(space_id)?;
1238        let (bpt, blob) = (cfg.bytes_per_token, cfg.blob_bytes);
1239        let cadence_now = cfg.snapshot_cadence == 1;
1240        let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1241        if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1242            return Err(Status::Busy);
1243        }
1244        let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1245        if sizing_gen != s.content_gen {
1246            return Err(Status::StaleSizing);
1247        }
1248        let required = ops::export_size(s, bpt, blob, range, encoding)?;
1249        if dst_len < required {
1250            return Err(Status::BufferTooSmall);
1251        }
1252        if s.kind == space_kind::RECURRENT_BLOB
1253            && !s.boundaries.contains(&range.end)
1254            && !(cadence_now && range.end == s.valid_len)
1255        {
1256            return Err(Status::OutOfBoundary);
1257        }
1258        let range = if s.is_blob_like() {
1259            TokenRange {
1260                start: range.end,
1261                end: range.end,
1262            }
1263        } else {
1264            range
1265        };
1266        if kind == OpKind::Demote
1267            && s.demoted
1268                .iter()
1269                .any(|(a, b, _)| range.start < *b && *a < range.end)
1270        {
1271            return Err(Status::RejectBounds);
1272        }
1273
1274        let payload = mock_payload(s, range, encoding, required - ops::ENVELOPE_BYTES as u64);
1275        let sealed = ops::seal(s, self.bundle_fp, range, encoding, &payload);
1276
1277        let handle = self.next_op;
1278        self.next_op += 1;
1279        let demote_range = (kind == OpKind::Demote).then_some((range.start, range.end, encoding));
1280        match &mut q.busy {
1281            Busy::Idle => {
1282                q.busy = Busy::OpActive([space_id].into_iter().collect());
1283            }
1284            Busy::OpActive(set) => {
1285                set.insert(space_id);
1286            }
1287            Busy::Ticking => unreachable!("checked above"),
1288        }
1289        self.ops.insert(
1290            handle,
1291            OpState {
1292                handle,
1293                seq,
1294                space_id,
1295                kind,
1296                state: op_state::RUNNING,
1297                error: 0,
1298                bytes_total: sealed.len() as u64,
1299                bytes_moved: 0,
1300                output: Some(sealed),
1301                staged: None,
1302                demote_range,
1303            },
1304        );
1305        Ok(handle)
1306    }
1307
1308    fn submit_import(
1309        &mut self,
1310        seq: SeqHandle,
1311        space_id: u32,
1312        src: &[u8],
1313        kind: OpKind,
1314    ) -> Result<OpHandle, Status> {
1315        self.space_ops_gate(space_id)?;
1316        let bundle_fp = self.bundle_fp;
1317        let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1318        if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1319            return Err(Status::Busy);
1320        }
1321        let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1322        let (env, _payload) = ops::open(src, s, bundle_fp)?;
1323        match kind {
1324            OpKind::Promote => {
1325                if !s
1326                    .demoted
1327                    .iter()
1328                    .any(|(a, b, _)| *a == env.range.start && *b == env.range.end)
1329                {
1330                    return Err(Status::RejectBounds);
1331                }
1332            }
1333            _ => {
1334                let blob = env.space_kind == space_kind::RECURRENT_BLOB
1335                    || env.space_kind == space_kind::RING_KV;
1336                let ok = if blob {
1337                    env.range.start == env.range.end
1338                } else {
1339                    env.range.start == 0 && env.range.end >= env.range.start
1340                };
1341                if !ok {
1342                    return Err(Status::EnvelopeMismatch);
1343                }
1344            }
1345        }
1346        let staged = ops::stage_import(&env, kind == OpKind::Promote);
1347
1348        let handle = self.next_op;
1349        self.next_op += 1;
1350        match &mut q.busy {
1351            Busy::Idle => {
1352                q.busy = Busy::OpActive([space_id].into_iter().collect());
1353            }
1354            Busy::OpActive(set) => {
1355                set.insert(space_id);
1356            }
1357            Busy::Ticking => unreachable!("checked above"),
1358        }
1359        self.ops.insert(
1360            handle,
1361            OpState {
1362                handle,
1363                seq,
1364                space_id,
1365                kind,
1366                state: op_state::RUNNING,
1367                error: 0,
1368                bytes_total: src.len() as u64,
1369                bytes_moved: 0,
1370                output: None,
1371                staged: Some(staged),
1372                demote_range: None,
1373            },
1374        );
1375        Ok(handle)
1376    }
1377
1378    fn release_op_claim(&mut self, seq: SeqHandle, space_id: u32) {
1379        if let Some(q) = self.seqs.get_mut(&seq) {
1380            if let Busy::OpActive(set) = &mut q.busy {
1381                set.remove(&space_id);
1382                if set.is_empty() {
1383                    q.busy = Busy::Idle;
1384                }
1385            }
1386        }
1387    }
1388
1389    fn complete_op(&mut self, handle: OpHandle) {
1390        let Some(o) = self.ops.get_mut(&handle) else {
1391            return;
1392        };
1393        if o.state != op_state::RUNNING {
1394            return;
1395        }
1396        o.state = op_state::DONE;
1397        o.bytes_moved = o.bytes_total;
1398        let (seq, space_id) = (o.seq, o.space_id);
1399        let staged = o.staged.take();
1400        let demote_range = o.demote_range.take();
1401        let bytes_moved = o.bytes_moved;
1402
1403        let cfg_bpt = self
1404            .cfg
1405            .spaces
1406            .iter()
1407            .find(|c| c.space_id == space_id)
1408            .map(|c| c.bytes_per_token)
1409            .unwrap_or(0);
1410        let mut pool_delta: i64 = 0;
1411        if let Some(q) = self.seqs.get_mut(&seq) {
1412            if let Some(s) = q.spaces.get_mut(&space_id) {
1413                if let Some(st) = staged {
1414                    if let Some((a, b)) = st.promote_range {
1415                        s.demoted.retain(|(x, y, _)| !(*x == a && *y == b));
1416                        pool_delta += ((b - a) * cfg_bpt) as i64;
1417                        if st.taint_bits != 0 {
1418                            s.taint_bits |= st.taint_bits;
1419                            s.provenance =
1420                                state::chain_digest(&s.provenance, state::prov_op::PROMOTE, b - a);
1421                        }
1422                    } else {
1423                        let old_resident = s
1424                            .valid_len
1425                            .saturating_sub(s.demoted.iter().map(|(a, b, _)| b - a).sum());
1426                        s.valid_len = st.valid_len;
1427                        s.provenance = st.provenance;
1428                        s.taint_bits = st.taint_bits;
1429                        s.demoted.clear();
1430                        if !st.boundaries.is_empty() {
1431                            s.boundaries = st.boundaries;
1432                        }
1433                        pool_delta +=
1434                            (st.valid_len * cfg_bpt) as i64 - (old_resident * cfg_bpt) as i64;
1435                    }
1436                    s.content_gen += 1;
1437                } else if let Some((a, b, encoding)) = demote_range {
1438                    s.demoted.push((a, b, encoding));
1439                    s.content_gen += 1;
1440                    pool_delta -= ((b - a) * cfg_bpt) as i64;
1441                }
1442            }
1443        }
1444        if pool_delta >= 0 {
1445            self.pool_used += pool_delta as u64;
1446        } else {
1447            self.pool_used = self.pool_used.saturating_sub((-pool_delta) as u64);
1448        }
1449        self.release_op_claim(seq, space_id);
1450        self.completions_pending.push(OpComplete {
1451            op: handle,
1452            state: op_state::DONE,
1453            _pad0: [0; 3],
1454            error: 0,
1455            bytes_moved,
1456        });
1457    }
1458}
1459
1460pub(crate) fn longest_common_prefix(a: &[u32], b: &[u32]) -> u64 {
1461    a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count() as u64
1462}
1463
1464fn mock_payload(
1465    s: &SpaceState,
1466    range: TokenRange,
1467    encoding: u8,
1468    payload_len: u64,
1469) -> Vec<u8> {
1470    let mut seedbuf = [0u8; 33];
1471    seedbuf[..8].copy_from_slice(&s.provenance[..8]);
1472    seedbuf[8..16].copy_from_slice(&range.start.to_le_bytes());
1473    seedbuf[16..24].copy_from_slice(&range.end.to_le_bytes());
1474    seedbuf[24..32].copy_from_slice(&s.valid_len.to_le_bytes());
1475    seedbuf[32] = encoding;
1476    let h = xxhash_rust::xxh3::xxh3_64(&seedbuf);
1477    let mut out = h
1478        .to_le_bytes()
1479        .repeat((payload_len as usize).div_ceil(8).max(1));
1480    out.truncate(payload_len as usize);
1481    out
1482}