Skip to main content

maskforge_core/structured/
matcher.rs

1use std::fmt;
2use std::sync::Arc;
3
4use crate::error::{CompileError, ErrorCode, LimitKind};
5use crate::index::{
6    BoundByteTrie, BoundedMask, TokenSliceCatalog, VocabFingerprint, VocabTrie, VocabularyHandle,
7};
8use crate::ir::SchemaIR;
9#[cfg(feature = "bench-internals")]
10use crate::ir::{ContainsPolicy, Node};
11use crate::mask::Bitmask;
12use crate::primitives::{NodeId, StateId, TokenId, TrieNodeId};
13
14use super::lexer::{DecodeStep, JsonStringDecoder};
15use super::limits::{StructuredLimits, StructuredRuntimeError};
16use super::plan::{PlanCompileFailure, StructuredPlan};
17#[cfg(any(test, feature = "bench-internals"))]
18use super::reference::ReferenceMatcher;
19use super::state::{
20    Checkpoint, ProofContext, SliceCertificate, SlicePreparation, SliceProof, StructuredState,
21};
22
23/// A compiled immutable structured program shared by sequence matchers.
24pub struct StructuredProgram {
25    #[cfg(feature = "bench-internals")]
26    ir: Arc<SchemaIR>,
27    backend: ProgramBackend,
28    limits: StructuredLimits,
29}
30
31enum ProgramBackend {
32    Incremental(Arc<StructuredPlan>),
33    #[cfg(feature = "bench-internals")]
34    ReferenceBench,
35}
36
37/// A mutable matcher with independent sequence state.
38pub struct StructuredMatcher {
39    program: Arc<StructuredProgram>,
40    backend: MatcherBackend,
41}
42
43enum MatcherBackend {
44    Incremental(IncrementalMatcher),
45    #[cfg(feature = "bench-internals")]
46    Reference(ReferenceMatcher),
47}
48
49struct IncrementalMatcher {
50    state: Box<StructuredState<'static>>,
51    events: Vec<TrieEvent>,
52    /// Reused by the read-only lexical prefix walk before its first semantic boundary.
53    lexical_path: Vec<u8>,
54    slice_proofs: SliceProofCache,
55    /// Reused across masks so a wrapper certificate allocates nothing on the warm path.
56    certificate_proofs: Vec<SliceProof>,
57    #[cfg(feature = "bench-internals")]
58    mask_work_metrics: MaskWorkMetrics,
59}
60
61#[derive(Clone, Copy, PartialEq, Eq)]
62struct SliceProofKey {
63    vocabulary: VocabFingerprint,
64    node: NodeId,
65    state: StateId,
66    body_prefix: bool,
67    reject_invalid: bool,
68}
69
70#[derive(Clone, Copy)]
71struct SliceProofEntry {
72    key: SliceProofKey,
73    subsumes: bool,
74}
75
76struct CandidateMaskEntry {
77    key: SliceProofKey,
78    mask: Arc<[u64]>,
79}
80
81#[derive(Default)]
82struct SliceProofCache {
83    entries: Vec<SliceProofEntry>,
84    candidate: Option<CandidateMaskEntry>,
85    next: usize,
86    #[cfg(feature = "bench-internals")]
87    hits: u64,
88    #[cfg(feature = "bench-internals")]
89    misses: u64,
90}
91
92impl SliceProofCache {
93    fn get(&mut self, key: SliceProofKey) -> Option<bool> {
94        let result = self
95            .entries
96            .iter()
97            .find(|entry| entry.key == key)
98            .map(|entry| entry.subsumes);
99        #[cfg(feature = "bench-internals")]
100        match result {
101            Some(_) => self.hits = self.hits.saturating_add(1),
102            None => self.misses = self.misses.saturating_add(1),
103        }
104        result
105    }
106
107    #[cfg(feature = "bench-internals")]
108    fn reset_measurements(&mut self) {
109        self.hits = 0;
110        self.misses = 0;
111    }
112
113    #[cfg(feature = "bench-internals")]
114    fn measurements(&self) -> SliceProofMetrics {
115        SliceProofMetrics {
116            hits: self.hits,
117            misses: self.misses,
118            entries: self.entries.len() + usize::from(self.candidate.is_some()),
119        }
120    }
121
122    fn insert(&mut self, key: SliceProofKey, subsumes: bool, byte_budget: usize) {
123        let slots = byte_budget / std::mem::size_of::<SliceProofEntry>();
124        if slots == 0 {
125            return;
126        }
127        if self.entries.capacity() == 0
128            && (self.entries.try_reserve_exact(slots).is_err()
129                || self
130                    .entries
131                    .capacity()
132                    .checked_mul(std::mem::size_of::<SliceProofEntry>())
133                    .is_none_or(|bytes| bytes > byte_budget))
134        {
135            self.entries = Vec::new();
136            return;
137        }
138        if self.entries.len() < self.entries.capacity() {
139            self.entries.push(SliceProofEntry { key, subsumes });
140            return;
141        }
142        self.entries[self.next] = SliceProofEntry { key, subsumes };
143        self.next = (self.next + 1) % self.entries.len();
144    }
145
146    fn candidate(&mut self, key: SliceProofKey) -> Option<Arc<[u64]>> {
147        let result = self
148            .candidate
149            .as_ref()
150            .filter(|entry| entry.key == key)
151            .map(|entry| entry.mask.clone());
152        #[cfg(feature = "bench-internals")]
153        match result {
154            Some(_) => self.hits = self.hits.saturating_add(1),
155            None => self.misses = self.misses.saturating_add(1),
156        }
157        result
158    }
159
160    fn insert_candidate(&mut self, key: SliceProofKey, mask: Arc<[u64]>, byte_budget: usize) {
161        let Some(bytes) = mask.len().checked_mul(std::mem::size_of::<u64>()) else {
162            return;
163        };
164        let Some(proof_bytes) = self
165            .entries
166            .capacity()
167            .checked_mul(std::mem::size_of::<SliceProofEntry>())
168        else {
169            return;
170        };
171        if proof_bytes
172            .checked_add(bytes)
173            .is_none_or(|total| total > byte_budget)
174        {
175            return;
176        }
177        self.candidate = Some(CandidateMaskEntry { key, mask });
178    }
179}
180
181/// Exact work performed by the most recent trie-mask operation.
182#[cfg(feature = "bench-internals")]
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
184pub struct MaskWorkMetrics {
185    /// Decoder/DFA edge transitions performed without mutating transactional matcher state.
186    pub local_dfa_transitions: u64,
187    /// Trie nodes whose visit event was processed.
188    pub trie_nodes: u64,
189    /// Trie edges whose byte was passed to the matcher.
190    pub trie_edges: u64,
191    /// Bytes passed to the matcher while walking trie edges.
192    pub pushed_bytes: u64,
193    /// State checkpoints created by the trie walk.
194    pub checkpoints: u64,
195    /// State rollbacks performed by the trie walk.
196    pub rollbacks: u64,
197    /// Dynamic references resolved while evaluating the mask.
198    pub dynamic_resolutions: u64,
199    /// Dynamic-scope slots inspected by those resolutions.
200    pub dynamic_scope_steps: u64,
201}
202
203/// Per-mask proof-cache activity exposed only to benchmark builds.
204#[cfg(feature = "bench-internals")]
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
206pub struct SliceProofMetrics {
207    /// Cached proof lookups that found an equal entry.
208    pub hits: u64,
209    /// Cached proof lookups that required a new proof attempt.
210    pub misses: u64,
211    /// Entries retained after the current mask operation.
212    pub entries: usize,
213}
214
215#[cfg(feature = "bench-internals")]
216#[derive(Clone, Copy)]
217enum MaskMetric {
218    LocalDfaTransition,
219    TrieNode,
220    TrieEdge,
221    PushedByte,
222    Checkpoint,
223    Rollback,
224}
225
226#[cfg(feature = "bench-internals")]
227impl IncrementalMatcher {
228    fn reset_mask_metrics(&mut self) {
229        self.mask_work_metrics = MaskWorkMetrics::default();
230        self.state.reset_dynamic_work_metrics();
231        self.slice_proofs.reset_measurements();
232    }
233
234    fn increment_mask_metric(&mut self, metric: MaskMetric) {
235        let counter = match metric {
236            MaskMetric::LocalDfaTransition => &mut self.mask_work_metrics.local_dfa_transitions,
237            MaskMetric::TrieNode => &mut self.mask_work_metrics.trie_nodes,
238            MaskMetric::TrieEdge => &mut self.mask_work_metrics.trie_edges,
239            MaskMetric::PushedByte => &mut self.mask_work_metrics.pushed_bytes,
240            MaskMetric::Checkpoint => &mut self.mask_work_metrics.checkpoints,
241            MaskMetric::Rollback => &mut self.mask_work_metrics.rollbacks,
242        };
243        *counter = counter.saturating_add(1);
244    }
245
246    fn finish_mask_metrics(&mut self) {
247        let (resolutions, steps) = self.state.dynamic_work_metrics();
248        self.mask_work_metrics.dynamic_resolutions = resolutions;
249        self.mask_work_metrics.dynamic_scope_steps = steps;
250    }
251}
252
253enum TrieEvent {
254    Visit {
255        node: TrieNodeId,
256        incoming_byte: Option<u8>,
257    },
258    Restore(Checkpoint),
259    LocalVisit {
260        node: TrieNodeId,
261        incoming_byte: Option<u8>,
262        decoder: JsonStringDecoder,
263        path_len: usize,
264    },
265    RestoreLocalPath(usize),
266}
267
268/// Errors from compilation, state advancement, and mask generation.
269#[derive(Clone, PartialEq, Eq, Debug)]
270pub enum StructuredMatcherError {
271    /// A schema or plan compilation error.
272    Compile(CompileError),
273    /// A bounded runtime resource was exhausted.
274    ResourceLimit {
275        /// Resource category that reached its cap.
276        kind: LimitKind,
277        /// Amount observed before rejection.
278        observed: usize,
279        /// Configured resource cap.
280        limit: usize,
281    },
282}
283
284impl fmt::Display for StructuredMatcherError {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        match self {
287            Self::Compile(error) => error.fmt(f),
288            Self::ResourceLimit {
289                kind,
290                observed,
291                limit,
292            } => write!(f, "structured resource limit {kind:?}: {observed}/{limit}"),
293        }
294    }
295}
296
297impl std::error::Error for StructuredMatcherError {}
298
299impl From<CompileError> for StructuredMatcherError {
300    fn from(error: CompileError) -> Self {
301        Self::Compile(error)
302    }
303}
304
305impl From<StructuredRuntimeError> for StructuredMatcherError {
306    fn from(error: StructuredRuntimeError) -> Self {
307        Self::ResourceLimit {
308            kind: error.kind,
309            observed: error.observed,
310            limit: error.limit,
311        }
312    }
313}
314
315impl StructuredProgram {
316    /// Compiles a schema using the default structured resource limits.
317    pub fn compile(ir: Arc<SchemaIR>) -> Result<Arc<Self>, CompileError> {
318        Self::compile_with_limits(ir, StructuredLimits::default())
319    }
320
321    pub(crate) fn compile_with_limits(
322        ir: Arc<SchemaIR>,
323        limits: StructuredLimits,
324    ) -> Result<Arc<Self>, CompileError> {
325        let backend = StructuredPlan::compile(ir.clone(), limits)
326            .map(|plan| ProgramBackend::Incremental(Arc::new(plan)))
327            .map_err(|PlanCompileFailure::Fatal(error)| error)?;
328        Ok(Arc::new(Self {
329            #[cfg(feature = "bench-internals")]
330            ir,
331            backend,
332            limits,
333        }))
334    }
335
336    /// Constructs the reference engine over the same IR for benchmark comparison.
337    #[doc(hidden)]
338    #[cfg(feature = "bench-internals")]
339    pub fn reference_for_bench(ir: Arc<SchemaIR>) -> Arc<Self> {
340        Arc::new(Self {
341            ir,
342            backend: ProgramBackend::ReferenceBench,
343            limits: StructuredLimits::default(),
344        })
345    }
346
347    /// Compiles a fresh structured plan and returns its isolated elapsed nanoseconds.
348    #[doc(hidden)]
349    #[cfg(feature = "bench-internals")]
350    pub fn compile_timed_for_bench(ir: Arc<SchemaIR>) -> Result<(Arc<Self>, u64), CompileError> {
351        let limits = StructuredLimits::default();
352        let started = std::time::Instant::now();
353        let plan = match StructuredPlan::compile(ir.clone(), limits) {
354            Ok(plan) => plan,
355            Err(PlanCompileFailure::Fatal(error)) => return Err(error),
356        };
357        let elapsed = u64::try_from(started.elapsed().as_nanos()).unwrap_or(u64::MAX);
358        let program = Arc::new(Self {
359            #[cfg(feature = "bench-internals")]
360            ir,
361            backend: ProgramBackend::Incremental(Arc::new(plan)),
362            limits,
363        });
364        Ok((program, elapsed))
365    }
366
367    /// Returns total plan-build and same-instance SCC-analysis nanoseconds separately.
368    #[doc(hidden)]
369    #[cfg(feature = "bench-internals")]
370    pub fn compile_profiled_for_bench(
371        ir: Arc<SchemaIR>,
372    ) -> Result<(Arc<Self>, u64, u64), CompileError> {
373        let limits = StructuredLimits::default();
374        let (plan, total_ns, scc_ns) = match StructuredPlan::compile_profiled(ir.clone(), limits) {
375            Ok(profile) => profile,
376            Err(PlanCompileFailure::Fatal(error)) => return Err(error),
377        };
378        let program = Arc::new(Self {
379            ir,
380            backend: ProgramBackend::Incremental(Arc::new(plan)),
381            limits,
382        });
383        Ok((program, total_ns, scc_ns))
384    }
385
386    /// Returns sorted reachable IR node-kind names for routing census builds.
387    #[doc(hidden)]
388    #[cfg(feature = "bench-internals")]
389    pub fn reachable_node_kinds_for_bench(
390        ir: &SchemaIR,
391    ) -> Result<Vec<&'static str>, CompileError> {
392        let mut seen = Vec::new();
393        seen.try_reserve_exact(ir.node_count()).map_err(|_| {
394            CompileError::new(
395                ErrorCode::InternalLimitExceeded,
396                crate::error::Stage::L3,
397                "routing census allocation",
398            )
399        })?;
400        seen.resize(ir.node_count(), false);
401        let mut stack = Vec::new();
402        stack.try_reserve_exact(ir.node_count()).map_err(|_| {
403            CompileError::new(
404                ErrorCode::InternalLimitExceeded,
405                crate::error::Stage::L3,
406                "routing census allocation",
407            )
408        })?;
409        stack.push(ir.root());
410        let mut kinds = Vec::new();
411        kinds.try_reserve_exact(ir.node_count()).map_err(|_| {
412            CompileError::new(
413                ErrorCode::InternalLimitExceeded,
414                crate::error::Stage::L3,
415                "routing census allocation",
416            )
417        })?;
418        while let Some(id) = stack.pop() {
419            let index = usize::try_from(id.get()).map_err(|_| {
420                CompileError::new(ErrorCode::Malformed, crate::error::Stage::L3, "node id")
421            })?;
422            let Some(slot) = seen.get_mut(index) else {
423                return Err(CompileError::new(
424                    ErrorCode::Malformed,
425                    crate::error::Stage::L3,
426                    "node id",
427                ));
428            };
429            if *slot {
430                continue;
431            }
432            *slot = true;
433            let node = ir.node(id).ok_or_else(|| {
434                CompileError::new(ErrorCode::Malformed, crate::error::Stage::L3, "node id")
435            })?;
436            kinds.push(node_kind_name(node));
437            stack.extend(crate::compile::node_children(ir, node)?);
438            match node {
439                Node::Object { dependent, .. } | Node::OpenObject { dependent, .. } => {
440                    stack.extend(
441                        ir.props_at(*dependent)
442                            .ok_or_else(|| {
443                                CompileError::new(
444                                    ErrorCode::Malformed,
445                                    crate::error::Stage::L3,
446                                    "dependent schema slice",
447                                )
448                            })?
449                            .iter()
450                            .map(|(_, child)| *child),
451                    );
452                }
453                Node::Array {
454                    contains:
455                        Some(crate::ir::ContainsConstraint {
456                            policy: ContainsPolicy::Schema(child),
457                            ..
458                        }),
459                    ..
460                }
461                | Node::Tuple {
462                    contains:
463                        Some(crate::ir::ContainsConstraint {
464                            policy: ContainsPolicy::Schema(child),
465                            ..
466                        }),
467                    ..
468                } => stack.push(*child),
469                Node::Ref { def } => stack.push(ir.def_target(*def).ok_or_else(|| {
470                    CompileError::new(
471                        ErrorCode::Malformed,
472                        crate::error::Stage::L3,
473                        "reference definition slot",
474                    )
475                })?),
476                _ => {}
477            }
478        }
479        kinds.sort_unstable();
480        kinds.dedup();
481        Ok(kinds)
482    }
483
484    /// Constructs a fresh mutable sequence matcher sharing this program.
485    pub fn new_matcher(self: &Arc<Self>) -> Result<StructuredMatcher, StructuredMatcherError> {
486        let backend = match &self.backend {
487            ProgramBackend::Incremental(plan) => MatcherBackend::Incremental(IncrementalMatcher {
488                state: Box::new(StructuredState::try_new(plan.clone())?),
489                events: Vec::new(),
490                lexical_path: Vec::new(),
491                slice_proofs: SliceProofCache::default(),
492                certificate_proofs: Vec::new(),
493                #[cfg(feature = "bench-internals")]
494                mask_work_metrics: MaskWorkMetrics::default(),
495            }),
496            #[cfg(feature = "bench-internals")]
497            ProgramBackend::ReferenceBench => MatcherBackend::Reference(
498                ReferenceMatcher::new_with_limits(self.ir.clone(), self.limits),
499            ),
500        };
501        Ok(StructuredMatcher {
502            program: self.clone(),
503            backend,
504        })
505    }
506
507    #[cfg(test)]
508    pub(crate) fn backend_kind(&self) -> &'static str {
509        match &self.backend {
510            ProgramBackend::Incremental(_) => "incremental",
511            #[cfg(feature = "bench-internals")]
512            ProgramBackend::ReferenceBench => "reference",
513        }
514    }
515
516    /// Whether this program uses the incremental backend.
517    #[must_use]
518    pub fn uses_incremental_backend(&self) -> bool {
519        matches!(self.backend, ProgramBackend::Incremental(_))
520    }
521
522    /// Benchmark diagnostic for the isolated oracle route.
523    #[cfg(any(test, feature = "bench-internals"))]
524    #[must_use]
525    pub fn fallback_reason(&self) -> Option<&'static str> {
526        None
527    }
528
529    /// Bytes retained by the compiled incremental plan; reference programs have no such plan.
530    #[must_use]
531    pub fn retained_plan_bytes(&self) -> Option<usize> {
532        match &self.backend {
533            ProgramBackend::Incremental(plan) => Some(plan.retained_bytes),
534            #[cfg(feature = "bench-internals")]
535            ProgramBackend::ReferenceBench => None,
536        }
537    }
538}
539
540/// Compiles and evaluates one complete document with the production incremental engine.
541pub fn try_accepts(ir: Arc<SchemaIR>, bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
542    let program = StructuredProgram::compile(ir)?;
543    let mut matcher = program.new_matcher()?;
544    if !bytes.is_empty() && !matcher.advance(bytes)? {
545        return Ok(false);
546    }
547    Ok(matcher.is_accepting() && matcher.eos_legal())
548}
549
550/// Compiles and evaluates whether a prefix can still reach a valid document.
551pub fn try_can_continue(ir: Arc<SchemaIR>, bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
552    let program = StructuredProgram::compile(ir)?;
553    let mut matcher = program.new_matcher()?;
554    if !bytes.is_empty() && !matcher.advance(bytes)? {
555        return Ok(false);
556    }
557    Ok(!matcher.is_dead())
558}
559
560#[cfg(feature = "bench-internals")]
561fn node_kind_name(node: &Node) -> &'static str {
562    match node {
563        Node::Null => "Null",
564        Node::Boolean => "Boolean",
565        Node::Never => "Never",
566        Node::StringConst { .. } => "StringConst",
567        Node::StringPattern { .. } => "StringPattern",
568        Node::Integer { .. } => "Integer",
569        Node::Number { .. } | Node::LexicalNumber { .. } => "Number",
570        Node::Enum { .. } => "Enum",
571        Node::Array { .. } => "Array",
572        Node::Tuple { .. } => "Tuple",
573        Node::Object { .. } => "Object",
574        Node::OpenObject { .. } => "OpenObject",
575        Node::Union { .. } => "Union",
576        Node::Intersection { .. } => "Intersection",
577        Node::ExactlyOne { .. } => "ExactlyOne",
578        Node::Not { .. } => "Not",
579        Node::Ref { .. } => "Ref",
580        Node::DynamicRef { .. } => "DynamicRef",
581        Node::Unevaluated { .. } => "Unevaluated",
582        Node::Unsupported { .. } => "Unsupported",
583    }
584}
585
586impl StructuredMatcher {
587    /// Returns work counters for the most recent trie-mask operation.
588    #[cfg(feature = "bench-internals")]
589    #[must_use]
590    pub fn mask_work_metrics_for_bench(&self) -> MaskWorkMetrics {
591        match &self.backend {
592            MatcherBackend::Incremental(incremental) => incremental.mask_work_metrics,
593            MatcherBackend::Reference(_) => MaskWorkMetrics::default(),
594        }
595    }
596
597    /// Returns proof-cache activity for the most recent trie-mask operation.
598    #[cfg(feature = "bench-internals")]
599    #[must_use]
600    pub fn slice_proof_metrics_for_bench(&self) -> SliceProofMetrics {
601        match &self.backend {
602            MatcherBackend::Incremental(incremental) => incremental.slice_proofs.measurements(),
603            MatcherBackend::Reference(_) => SliceProofMetrics::default(),
604        }
605    }
606
607    /// Bytes retained by the local slice-proof cache.
608    #[cfg(feature = "bench-internals")]
609    #[must_use]
610    pub fn slice_proof_cache_bytes_for_bench(&self) -> usize {
611        match &self.backend {
612            MatcherBackend::Incremental(incremental) => incremental
613                .slice_proofs
614                .entries
615                .capacity()
616                .saturating_mul(std::mem::size_of::<SliceProofEntry>()),
617            MatcherBackend::Reference(_) => 0,
618        }
619    }
620
621    /// Constructs a matcher from an already compiled program.
622    pub fn new(program: Arc<StructuredProgram>) -> Result<Self, StructuredMatcherError> {
623        program.new_matcher()
624    }
625
626    /// Runtime backend selected for this matcher.
627    #[must_use]
628    pub fn backend_name(&self) -> &'static str {
629        match &self.backend {
630            MatcherBackend::Incremental(_) => "incremental",
631            #[cfg(feature = "bench-internals")]
632            MatcherBackend::Reference(_) => "reference",
633        }
634    }
635
636    /// Whether the current state accepts a complete document.
637    #[must_use]
638    pub fn is_accepting(&self) -> bool {
639        match &self.backend {
640            MatcherBackend::Incremental(incremental) => incremental.state.is_accepting(),
641            #[cfg(feature = "bench-internals")]
642            MatcherBackend::Reference(reference) => reference.is_accepting(),
643        }
644    }
645
646    /// Whether the current state is a permanent dead end.
647    #[must_use]
648    pub fn is_dead(&self) -> bool {
649        match &self.backend {
650            MatcherBackend::Incremental(incremental) => incremental.state.is_dead(),
651            #[cfg(feature = "bench-internals")]
652            MatcherBackend::Reference(reference) => reference.is_dead(),
653        }
654    }
655
656    /// Whether end-of-stream is legal at the current state.
657    #[must_use]
658    pub fn eos_legal(&self) -> bool {
659        match &self.backend {
660            MatcherBackend::Incremental(incremental) => incremental.state.is_accepting(),
661            #[cfg(feature = "bench-internals")]
662            MatcherBackend::Reference(reference) => reference.eos_legal(),
663        }
664    }
665
666    /// Bytes currently retained by this sequence's mutable backend state.
667    #[must_use]
668    pub fn retained_session_bytes(&self) -> usize {
669        match &self.backend {
670            MatcherBackend::Incremental(incremental) => incremental.state.retained_bytes(),
671            #[cfg(feature = "bench-internals")]
672            MatcherBackend::Reference(reference) => reference.retained_bytes(),
673        }
674    }
675
676    /// Peak charged session bytes observed by this matcher.
677    #[must_use]
678    pub fn peak_session_bytes(&self) -> usize {
679        match &self.backend {
680            MatcherBackend::Incremental(incremental) => incremental.state.peak_session_bytes(),
681            #[cfg(feature = "bench-internals")]
682            MatcherBackend::Reference(reference) => reference.retained_bytes(),
683        }
684    }
685
686    /// Number of validator cursors currently owned by this matcher tree.
687    #[cfg(feature = "bench-internals")]
688    #[must_use]
689    pub fn active_validator_count_for_bench(&self) -> usize {
690        match &self.backend {
691            MatcherBackend::Incremental(incremental) => {
692                incremental.state.active_validator_count_for_bench()
693            }
694            MatcherBackend::Reference(_) => 0,
695        }
696    }
697
698    /// Returns `(frame depth, wrapper-frame depth)` for benchmark diagnostics.
699    #[cfg(feature = "bench-internals")]
700    #[must_use]
701    pub fn frame_profile_for_bench(&self) -> (usize, usize) {
702        match &self.backend {
703            MatcherBackend::Incremental(incremental) => incremental.state.frame_profile_for_bench(),
704            MatcherBackend::Reference(_) => (0, 0),
705        }
706    }
707
708    /// Returns `(total, undo)` mutable retention for benchmark-only diagnostics.
709    #[cfg(feature = "bench-internals")]
710    #[must_use]
711    pub fn retention_breakdown_for_bench(&self) -> (usize, usize) {
712        match &self.backend {
713            MatcherBackend::Incremental(incremental) => {
714                incremental.state.retention_breakdown_for_bench()
715            }
716            MatcherBackend::Reference(reference) => (reference.retained_bytes(), 0),
717        }
718    }
719
720    /// Atomically commits a whole tokenizer token.
721    pub fn advance(&mut self, token_bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
722        if token_bytes.is_empty() {
723            return Ok(false);
724        }
725        match &mut self.backend {
726            MatcherBackend::Incremental(incremental) => {
727                let state = &mut incremental.state;
728                let mark = state.checkpoint();
729                for &byte in token_bytes {
730                    match state.try_push_byte(byte) {
731                        Ok(true) => {}
732                        Ok(false) => {
733                            state.rollback(mark);
734                            return Ok(false);
735                        }
736                        Err(error) => {
737                            state.rollback(mark);
738                            return Err(error.into());
739                        }
740                    }
741                }
742                if let Err(error) = state.commit_checkpoint() {
743                    state.rollback(mark);
744                    return Err(error.into());
745                }
746                Ok(true)
747            }
748            #[cfg(feature = "bench-internals")]
749            MatcherBackend::Reference(reference) => reference
750                .try_advance(token_bytes)
751                .map_err(StructuredMatcherError::from),
752        }
753    }
754
755    /// Computes a mask by speculatively feeding each candidate through one reusable state.
756    pub fn allowed_mask_from_records<'a>(
757        &mut self,
758        mask_vocab_size: usize,
759        records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
760    ) -> Result<Bitmask, StructuredMatcherError> {
761        match &mut self.backend {
762            MatcherBackend::Incremental(incremental) => {
763                let state = &mut incremental.state;
764                let mut mask = Bitmask::zeros(mask_vocab_size);
765                let mut work = 0u64;
766                for (bytes, ids) in records {
767                    charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
768                    if candidate_is_allowed(state, bytes)? {
769                        for &id in ids {
770                            mask.set(TokenId(id))
771                                .map_err(StructuredMatcherError::from)?;
772                        }
773                    }
774                }
775                Ok(mask)
776            }
777            #[cfg(feature = "bench-internals")]
778            MatcherBackend::Reference(reference) => reference
779                .allowed_mask_from_records(mask_vocab_size, records)
780                .map_err(StructuredMatcherError::from),
781        }
782    }
783
784    /// Clears and fills a packed `u32`-word mask by scanning vocabulary records, without an
785    /// intermediate byte buffer. `O(1)` additional allocation beyond `out`.
786    pub fn write_record_mask_words_into<'a>(
787        &mut self,
788        mask_vocab_size: usize,
789        records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
790        out: &mut [u32],
791    ) -> Result<(), StructuredMatcherError> {
792        validate_mask_output_words(mask_vocab_size, out)?;
793        out.fill(0);
794        let result = match &mut self.backend {
795            MatcherBackend::Incremental(incremental) => (|| {
796                let mut work = 0u64;
797                for (bytes, ids) in records {
798                    charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
799                    if candidate_is_allowed(&mut incremental.state, bytes)? {
800                        for &id in ids {
801                            set_token_bit_word(out, mask_vocab_size, id)?;
802                        }
803                    }
804                }
805                Ok(())
806            })(),
807            #[cfg(feature = "bench-internals")]
808            MatcherBackend::Reference(reference) => reference
809                .allowed_mask_from_records(mask_vocab_size, records)
810                .map(|mask| out.copy_from_slice(mask.as_words()))
811                .map_err(StructuredMatcherError::from),
812        };
813        if result.is_err() {
814            out.fill(0);
815        }
816        result
817    }
818
819    /// Clears and fills a little-endian packed mask by scanning vocabulary records.
820    pub fn write_record_mask_le_bytes_into<'a>(
821        &mut self,
822        mask_vocab_size: usize,
823        records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
824        out: &mut [u8],
825    ) -> Result<(), StructuredMatcherError> {
826        validate_mask_output(mask_vocab_size, out)?;
827        out.fill(0);
828        let result = match &mut self.backend {
829            MatcherBackend::Incremental(incremental) => (|| {
830                let mut work = 0u64;
831                for (bytes, ids) in records {
832                    charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
833                    if candidate_is_allowed(&mut incremental.state, bytes)? {
834                        for &id in ids {
835                            set_token_bit_le(out, mask_vocab_size, id)?;
836                        }
837                    }
838                }
839                Ok(())
840            })(),
841            #[cfg(feature = "bench-internals")]
842            MatcherBackend::Reference(reference) => reference
843                .write_mask_le_bytes_into(mask_vocab_size, records, out)
844                .map_err(StructuredMatcherError::from),
845        };
846        if result.is_err() {
847            out.fill(0);
848        }
849        result
850    }
851
852    /// Clears and fills a little-endian packed mask without allocating a temporary `Bitmask`.
853    pub fn write_mask_le_bytes_into(
854        &mut self,
855        vocabulary: &VocabularyHandle,
856        trie: Option<&BoundByteTrie>,
857        out: &mut [u8],
858    ) -> Result<(), StructuredMatcherError> {
859        let width = vocabulary.mask_vocab_size();
860        validate_mask_output(width, out)?;
861        out.fill(0);
862        let result = match &mut self.backend {
863            MatcherBackend::Incremental(incremental) => {
864                let trie = trie.ok_or_else(missing_trie_error)?;
865                walk_trie_mask(incremental, vocabulary, trie, out, self.program.limits)
866            }
867            #[cfg(feature = "bench-internals")]
868            MatcherBackend::Reference(reference) => reference
869                .write_mask_le_bytes_into(width, vocabulary.iter_records(), out)
870                .map_err(StructuredMatcherError::from),
871        };
872        if result.is_err() {
873            out.fill(0);
874        }
875        result
876    }
877
878    /// Bypassing slice selection lets real-tokenizer benchmarks verify every emitted bit.
879    #[cfg(feature = "bench-internals")]
880    pub fn write_full_trie_mask_for_bench(
881        &mut self,
882        vocabulary: &VocabularyHandle,
883        trie: &BoundByteTrie,
884        out: &mut [u8],
885    ) -> Result<(), StructuredMatcherError> {
886        validate_mask_output(vocabulary.mask_vocab_size(), out)?;
887        out.fill(0);
888        let result = match &mut self.backend {
889            MatcherBackend::Incremental(incremental) => {
890                if trie.fingerprint() != vocabulary.fingerprint() {
891                    return Err(missing_trie_error());
892                }
893                walk_trie_mask_inner(
894                    incremental,
895                    vocabulary,
896                    trie.trie(),
897                    out,
898                    self.program.limits,
899                    &mut 0,
900                )
901            }
902            MatcherBackend::Reference(reference) => reference
903                .write_mask_le_bytes_into(
904                    vocabulary.mask_vocab_size(),
905                    vocabulary.iter_records(),
906                    out,
907                )
908                .map_err(StructuredMatcherError::from),
909        };
910        if result.is_err() {
911            out.fill(0);
912        }
913        result
914    }
915}
916
917fn validate_mask_output(width: usize, out: &[u8]) -> Result<(), StructuredMatcherError> {
918    let bytes = width
919        .div_ceil(32)
920        .checked_mul(std::mem::size_of::<u32>())
921        .ok_or_else(mask_output_error)?;
922    if out.len() != bytes {
923        return Err(mask_output_error());
924    }
925    Ok(())
926}
927
928fn mask_output_error() -> StructuredMatcherError {
929    CompileError::new(
930        ErrorCode::ArtifactOutOfBounds,
931        crate::error::Stage::L4Bind,
932        "structured mask output length does not match vocabulary width",
933    )
934    .into()
935}
936
937fn missing_trie_error() -> StructuredMatcherError {
938    CompileError::new(
939        ErrorCode::ArtifactMismatch,
940        crate::error::Stage::L4Bind,
941        "incremental structured matcher requires its bound byte trie",
942    )
943    .into()
944}
945
946fn set_token_bit_le(
947    out: &mut [u8],
948    width: usize,
949    token: u32,
950) -> Result<(), StructuredMatcherError> {
951    let token = usize::try_from(token).map_err(|_| mask_output_error())?;
952    if token >= width {
953        return Err(mask_output_error());
954    }
955    let byte = token.checked_div(8).ok_or_else(mask_output_error)?;
956    let bit = u32::try_from(token % 8).map_err(|_| mask_output_error())?;
957    out[byte] |= 1u8 << bit;
958    Ok(())
959}
960
961fn validate_mask_output_words(width: usize, out: &[u32]) -> Result<(), StructuredMatcherError> {
962    if out.len() != width.div_ceil(32) {
963        return Err(mask_output_error());
964    }
965    Ok(())
966}
967
968fn set_token_bit_word(
969    out: &mut [u32],
970    width: usize,
971    token: u32,
972) -> Result<(), StructuredMatcherError> {
973    let token = usize::try_from(token).map_err(|_| mask_output_error())?;
974    if token >= width {
975        return Err(mask_output_error());
976    }
977    out[token / 32] |= 1u32 << (token % 32);
978    Ok(())
979}
980
981fn candidate_is_allowed(
982    state: &mut StructuredState<'static>,
983    bytes: &[u8],
984) -> Result<bool, StructuredMatcherError> {
985    if bytes.is_empty() {
986        return Ok(false);
987    }
988    state.validate_retained_budget()?;
989    let mark = state.checkpoint();
990    for &byte in bytes {
991        match state.try_push_byte_for_mask(byte) {
992            Ok(true) => {}
993            Ok(false) => {
994                state.rollback(mark);
995                return Ok(false);
996            }
997            Err(error) => {
998                state.rollback(mark);
999                return Err(error.into());
1000            }
1001        }
1002    }
1003    state.rollback(mark);
1004    Ok(true)
1005}
1006
1007fn walk_trie_mask(
1008    incremental: &mut IncrementalMatcher,
1009    vocabulary: &VocabularyHandle,
1010    bound: &BoundByteTrie,
1011    out: &mut [u8],
1012    limits: StructuredLimits,
1013) -> Result<(), StructuredMatcherError> {
1014    #[cfg(feature = "bench-internals")]
1015    incremental.reset_mask_metrics();
1016    if bound.fingerprint() != vocabulary.fingerprint() {
1017        return Err(missing_trie_error());
1018    }
1019    let (selected, mut proof_work) = select_adaptive_trie(incremental, vocabulary, limits)?;
1020    if let Some(slice) = selected {
1021        match slice.bounded {
1022            Some(mask) => or_bounded_mask(out, mask, slice.candidate.as_deref()),
1023            None => or_token_mask(
1024                out,
1025                slice.catalog.safe().token_mask(),
1026                slice.candidate.as_deref(),
1027            ),
1028        }
1029        if slice.body_prefix {
1030            or_token_mask(
1031                out,
1032                slice.catalog.body_extra().token_mask(),
1033                slice.candidate.as_deref(),
1034            );
1035        }
1036        let mut result = if slice.reject_invalid {
1037            walk_body_boundary_trie_mask_inner(
1038                incremental,
1039                vocabulary,
1040                slice.catalog.body_boundary_trie(),
1041                slice.catalog.body_boundary_max_bytes(),
1042                out,
1043                limits,
1044                &mut proof_work,
1045            )
1046        } else {
1047            walk_trie_mask_inner(
1048                incremental,
1049                vocabulary,
1050                if slice.body_prefix {
1051                    slice.catalog.body_uncertain_trie()
1052                } else {
1053                    slice.catalog.uncertain_trie()
1054                },
1055                out,
1056                limits,
1057                &mut proof_work,
1058            )
1059        };
1060        if result.is_ok() && slice.bounded.is_some_and(|mask| mask.needs_overflow_walk()) {
1061            result = walk_trie_mask_inner(
1062                incremental,
1063                vocabulary,
1064                slice.catalog.scalar_overflow_trie(),
1065                out,
1066                limits,
1067                &mut proof_work,
1068            );
1069        }
1070        #[cfg(feature = "bench-internals")]
1071        incremental.finish_mask_metrics();
1072        return result;
1073    }
1074    let result = walk_trie_mask_inner(
1075        incremental,
1076        vocabulary,
1077        bound.trie(),
1078        out,
1079        limits,
1080        &mut proof_work,
1081    );
1082    #[cfg(feature = "bench-internals")]
1083    incremental.finish_mask_metrics();
1084    result
1085}
1086
1087fn walk_trie_mask_inner(
1088    incremental: &mut IncrementalMatcher,
1089    vocabulary: &VocabularyHandle,
1090    trie: &VocabTrie,
1091    out: &mut [u8],
1092    limits: StructuredLimits,
1093    work: &mut u64,
1094) -> Result<(), StructuredMatcherError> {
1095    incremental.state.validate_retained_budget()?;
1096    let width = vocabulary.mask_vocab_size();
1097    let root = incremental.state.checkpoint();
1098    #[cfg(feature = "bench-internals")]
1099    incremental.increment_mask_metric(MaskMetric::Checkpoint);
1100    incremental.events.clear();
1101    reserve_events(&mut incremental.events, 1, limits)?;
1102    incremental.events.push(TrieEvent::Visit {
1103        node: TrieNodeId(0),
1104        incoming_byte: None,
1105    });
1106    let result = (|| {
1107        while let Some(event) = incremental.events.pop() {
1108            match event {
1109                TrieEvent::Restore(mark) => {
1110                    incremental.state.rollback(mark);
1111                    #[cfg(feature = "bench-internals")]
1112                    incremental.increment_mask_metric(MaskMetric::Rollback);
1113                }
1114                TrieEvent::Visit {
1115                    node,
1116                    incoming_byte,
1117                } => {
1118                    #[cfg(feature = "bench-internals")]
1119                    incremental.increment_mask_metric(MaskMetric::TrieNode);
1120                    let mark = incremental.state.checkpoint();
1121                    #[cfg(feature = "bench-internals")]
1122                    incremental.increment_mask_metric(MaskMetric::Checkpoint);
1123                    if let Some(byte) = incoming_byte {
1124                        #[cfg(feature = "bench-internals")]
1125                        {
1126                            incremental.increment_mask_metric(MaskMetric::TrieEdge);
1127                            incremental.increment_mask_metric(MaskMetric::PushedByte);
1128                        }
1129                        match incremental.state.try_push_byte_for_mask(byte) {
1130                            Ok(true) => {}
1131                            Ok(false) => {
1132                                incremental.state.rollback(mark);
1133                                #[cfg(feature = "bench-internals")]
1134                                incremental.increment_mask_metric(MaskMetric::Rollback);
1135                                continue;
1136                            }
1137                            Err(error) => return Err(error.into()),
1138                        }
1139                    }
1140                    for &token in trie.leaf_tokens_of(node)? {
1141                        charge_mask_work(work, 0, 1, limits)?;
1142                        set_token_bit_le(out, width, token)?;
1143                    }
1144                    let (keys, children) = trie.children(node)?;
1145                    let event_count = children.len().checked_add(1).ok_or({
1146                        StructuredMatcherError::ResourceLimit {
1147                            kind: LimitKind::MaskScratchBytes,
1148                            observed: usize::MAX,
1149                            limit: limits.max_mask_scratch_bytes,
1150                        }
1151                    })?;
1152                    reserve_events(&mut incremental.events, event_count, limits)?;
1153                    incremental.events.push(TrieEvent::Restore(mark));
1154                    for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1155                        let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1156                        charge_mask_work(work, 1, 0, limits)?;
1157                        incremental.events.push(TrieEvent::Visit {
1158                            node: child,
1159                            incoming_byte: Some(byte),
1160                        });
1161                    }
1162                }
1163                TrieEvent::LocalVisit { .. } | TrieEvent::RestoreLocalPath(_) => {
1164                    unreachable!("local lexical event in exact trie walk")
1165                }
1166            }
1167        }
1168        Ok(())
1169    })();
1170    incremental.state.rollback(root);
1171    #[cfg(feature = "bench-internals")]
1172    incremental.increment_mask_metric(MaskMetric::Rollback);
1173    result
1174}
1175
1176/// Walks tokens that cross a JSON-string closing quote.
1177/// Replays the shared prefix under one checkpoint before mutating matcher state.
1178fn walk_body_boundary_trie_mask_inner(
1179    incremental: &mut IncrementalMatcher,
1180    vocabulary: &VocabularyHandle,
1181    trie: &VocabTrie,
1182    max_path_bytes: usize,
1183    out: &mut [u8],
1184    limits: StructuredLimits,
1185    work: &mut u64,
1186) -> Result<(), StructuredMatcherError> {
1187    incremental.state.validate_retained_budget()?;
1188    reserve_lexical_path(incremental, max_path_bytes, limits)?;
1189    let width = vocabulary.mask_vocab_size();
1190    let root = incremental.state.checkpoint();
1191    incremental.events.clear();
1192    incremental.lexical_path.clear();
1193    reserve_events_with_extra(
1194        &mut incremental.events,
1195        1,
1196        incremental.lexical_path.capacity(),
1197        limits,
1198    )?;
1199    incremental.events.push(TrieEvent::LocalVisit {
1200        node: TrieNodeId(0),
1201        incoming_byte: None,
1202        decoder: JsonStringDecoder::new(),
1203        path_len: 0,
1204    });
1205    let result = (|| {
1206        while let Some(event) = incremental.events.pop() {
1207            match event {
1208                TrieEvent::Restore(mark) => {
1209                    incremental.state.rollback(mark);
1210                    #[cfg(feature = "bench-internals")]
1211                    incremental.increment_mask_metric(MaskMetric::Rollback);
1212                }
1213                TrieEvent::RestoreLocalPath(len) => incremental.lexical_path.truncate(len),
1214                TrieEvent::LocalVisit {
1215                    node,
1216                    incoming_byte,
1217                    mut decoder,
1218                    path_len,
1219                } => {
1220                    incremental.lexical_path.truncate(path_len);
1221                    let boundary = if let Some(byte) = incoming_byte {
1222                        if incremental.lexical_path.len() == max_path_bytes {
1223                            return Err(StructuredMatcherError::ResourceLimit {
1224                                kind: LimitKind::MaskScratchBytes,
1225                                observed: max_path_bytes.saturating_add(1),
1226                                limit: limits.max_mask_scratch_bytes,
1227                            });
1228                        }
1229                        incremental.lexical_path.push(byte);
1230                        if byte == b'"' && decoder.at_boundary() {
1231                            true
1232                        } else {
1233                            #[cfg(feature = "bench-internals")]
1234                            incremental.increment_mask_metric(MaskMetric::LocalDfaTransition);
1235                            if decoder.push(byte) == DecodeStep::Invalid {
1236                                incremental.lexical_path.truncate(path_len);
1237                                continue;
1238                            }
1239                            false
1240                        }
1241                    } else {
1242                        false
1243                    };
1244
1245                    if boundary {
1246                        #[cfg(feature = "bench-internals")]
1247                        incremental.increment_mask_metric(MaskMetric::TrieNode);
1248                        let mark = incremental.state.checkpoint();
1249                        #[cfg(feature = "bench-internals")]
1250                        incremental.increment_mask_metric(MaskMetric::Checkpoint);
1251                        let mut accepted = true;
1252                        for index in 0..incremental.lexical_path.len() {
1253                            let byte = incremental.lexical_path[index];
1254                            #[cfg(feature = "bench-internals")]
1255                            {
1256                                incremental.increment_mask_metric(MaskMetric::TrieEdge);
1257                                incremental.increment_mask_metric(MaskMetric::PushedByte);
1258                            }
1259                            if !incremental.state.try_push_byte_for_mask(byte)? {
1260                                accepted = false;
1261                                break;
1262                            }
1263                        }
1264                        if !accepted {
1265                            incremental.state.rollback(mark);
1266                            #[cfg(feature = "bench-internals")]
1267                            incremental.increment_mask_metric(MaskMetric::Rollback);
1268                            incremental.lexical_path.truncate(path_len);
1269                            continue;
1270                        }
1271                        for &token in trie.leaf_tokens_of(node)? {
1272                            charge_mask_work(work, 0, 1, limits)?;
1273                            set_token_bit_le(out, width, token)?;
1274                        }
1275                        let (keys, children) = trie.children(node)?;
1276                        let event_count = children.len().checked_add(1).ok_or(
1277                            StructuredMatcherError::ResourceLimit {
1278                                kind: LimitKind::MaskScratchBytes,
1279                                observed: usize::MAX,
1280                                limit: limits.max_mask_scratch_bytes,
1281                            },
1282                        )?;
1283                        reserve_events_with_extra(
1284                            &mut incremental.events,
1285                            event_count,
1286                            incremental.lexical_path.capacity(),
1287                            limits,
1288                        )?;
1289                        incremental.events.push(TrieEvent::Restore(mark));
1290                        for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1291                            let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1292                            charge_mask_work(work, 1, 0, limits)?;
1293                            #[cfg(feature = "bench-internals")]
1294                            incremental.increment_mask_metric(MaskMetric::LocalDfaTransition);
1295                            if incremental.state.next_byte_is_lexically_impossible(byte) {
1296                                continue;
1297                            }
1298                            incremental.events.push(TrieEvent::Visit {
1299                                node: child,
1300                                incoming_byte: Some(byte),
1301                            });
1302                        }
1303                        incremental.lexical_path.truncate(path_len);
1304                        continue;
1305                    }
1306
1307                    debug_assert!(trie.leaf_tokens_of(node)?.is_empty());
1308                    let (keys, children) = trie.children(node)?;
1309                    let event_count = children.len().checked_add(1).ok_or(
1310                        StructuredMatcherError::ResourceLimit {
1311                            kind: LimitKind::MaskScratchBytes,
1312                            observed: usize::MAX,
1313                            limit: limits.max_mask_scratch_bytes,
1314                        },
1315                    )?;
1316                    reserve_events_with_extra(
1317                        &mut incremental.events,
1318                        event_count,
1319                        incremental.lexical_path.capacity(),
1320                        limits,
1321                    )?;
1322                    incremental
1323                        .events
1324                        .push(TrieEvent::RestoreLocalPath(path_len));
1325                    let child_path_len = incremental.lexical_path.len();
1326                    for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1327                        let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1328                        charge_mask_work(work, 1, 0, limits)?;
1329                        incremental.events.push(TrieEvent::LocalVisit {
1330                            node: child,
1331                            incoming_byte: Some(byte),
1332                            decoder,
1333                            path_len: child_path_len,
1334                        });
1335                    }
1336                }
1337                TrieEvent::Visit {
1338                    node,
1339                    incoming_byte,
1340                } => {
1341                    #[cfg(feature = "bench-internals")]
1342                    incremental.increment_mask_metric(MaskMetric::TrieNode);
1343                    let mark = incremental.state.checkpoint();
1344                    #[cfg(feature = "bench-internals")]
1345                    incremental.increment_mask_metric(MaskMetric::Checkpoint);
1346                    if let Some(byte) = incoming_byte {
1347                        #[cfg(feature = "bench-internals")]
1348                        {
1349                            incremental.increment_mask_metric(MaskMetric::TrieEdge);
1350                            incremental.increment_mask_metric(MaskMetric::PushedByte);
1351                        }
1352                        match incremental.state.try_push_byte_for_mask(byte) {
1353                            Ok(true) => {}
1354                            Ok(false) => {
1355                                incremental.state.rollback(mark);
1356                                #[cfg(feature = "bench-internals")]
1357                                incremental.increment_mask_metric(MaskMetric::Rollback);
1358                                continue;
1359                            }
1360                            Err(error) => return Err(error.into()),
1361                        }
1362                    }
1363                    for &token in trie.leaf_tokens_of(node)? {
1364                        charge_mask_work(work, 0, 1, limits)?;
1365                        set_token_bit_le(out, width, token)?;
1366                    }
1367                    let (keys, children) = trie.children(node)?;
1368                    let event_count = children.len().checked_add(1).ok_or(
1369                        StructuredMatcherError::ResourceLimit {
1370                            kind: LimitKind::MaskScratchBytes,
1371                            observed: usize::MAX,
1372                            limit: limits.max_mask_scratch_bytes,
1373                        },
1374                    )?;
1375                    reserve_events_with_extra(
1376                        &mut incremental.events,
1377                        event_count,
1378                        incremental.lexical_path.capacity(),
1379                        limits,
1380                    )?;
1381                    incremental.events.push(TrieEvent::Restore(mark));
1382                    for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1383                        let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1384                        charge_mask_work(work, 1, 0, limits)?;
1385                        incremental.events.push(TrieEvent::Visit {
1386                            node: child,
1387                            incoming_byte: Some(byte),
1388                        });
1389                    }
1390                }
1391            }
1392        }
1393        Ok(())
1394    })();
1395    incremental.state.rollback(root);
1396    result
1397}
1398
1399/// Computes slice eligibility when the innermost frame is a wrapper.
1400/// Wrapper cursors provide the lexical automata requiring trie-inclusion proof.
1401fn select_wrapped_slice<'a>(
1402    incremental: &mut IncrementalMatcher,
1403    vocabulary: &'a VocabularyHandle,
1404    limits: StructuredLimits,
1405    body_prefix: bool,
1406) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1407    let Some(catalog) = vocabulary.token_slices().map(AsRef::as_ref) else {
1408        return Ok((None, 0));
1409    };
1410    if catalog.safe().scalar_count().is_some() {
1411        return Ok((None, 0));
1412    }
1413    let mut proofs = std::mem::take(&mut incremental.certificate_proofs);
1414    proofs.clear();
1415    let max_bytes = catalog.safe().max_byte_len().max(if body_prefix {
1416        catalog
1417            .body_extra()
1418            .max_byte_len()
1419            .max(catalog.body_boundary_max_bytes())
1420            .max(catalog.body_invalid_max_bytes())
1421    } else {
1422        0
1423    });
1424    let Some(certificate_fuel) = incremental.state.certificate_fuel() else {
1425        incremental.certificate_proofs = proofs;
1426        return Ok((None, 0));
1427    };
1428    let mut fuel = ProofContext::for_preparation(
1429        max_bytes,
1430        incremental.state.retained_bytes(),
1431        limits.max_session_bytes,
1432        certificate_fuel,
1433    );
1434    fuel.body_prefix = body_prefix;
1435    let certificate = incremental.state.certify_slice(&mut proofs, &mut fuel);
1436    if certificate == SliceCertificate::Unknown {
1437        incremental.certificate_proofs = proofs;
1438        return Ok((None, 0));
1439    }
1440    let bounded = match fuel.remaining_scalars {
1441        Some(remaining) => {
1442            let Some(mask) = catalog.bounded_mask(max_bytes, Some(remaining)) else {
1443                incremental.certificate_proofs = proofs;
1444                return Ok((None, 0));
1445            };
1446            Some(mask)
1447        }
1448        None => None,
1449    };
1450    let mut work = 0u64;
1451    let mut proved = true;
1452    let mut reject_invalid = body_prefix;
1453    let mut candidate = None;
1454    for proof in proofs.iter().copied() {
1455        let (node, state, requires_live) = match proof {
1456            SliceProof::All(node, state) => (node, state, false),
1457            SliceProof::CandidateLive(node, state) => (node, state, true),
1458        };
1459        let key = SliceProofKey {
1460            vocabulary: vocabulary.fingerprint(),
1461            node,
1462            state,
1463            body_prefix,
1464            reject_invalid: false,
1465        };
1466        let Some(engine) = incremental.state.engine_for_node(node) else {
1467            proved = false;
1468            break;
1469        };
1470        if requires_live {
1471            if candidate.is_some() {
1472                proved = false;
1473                break;
1474            }
1475            let mask = if let Some(cached) = incremental.slice_proofs.candidate(key) {
1476                cached
1477            } else {
1478                let Some((mask, spent)) = candidate_live_mask(
1479                    engine,
1480                    state,
1481                    catalog.safe().filtered_trie(),
1482                    body_prefix.then_some(catalog.body_extra().filtered_trie()),
1483                    vocabulary.mask_vocab_size(),
1484                    limits,
1485                ) else {
1486                    proved = false;
1487                    break;
1488                };
1489                work = work.saturating_add(spent);
1490                incremental.slice_proofs.insert_candidate(
1491                    key,
1492                    mask.clone(),
1493                    limits.max_slice_cache_bytes,
1494                );
1495                mask
1496            };
1497            candidate = Some(mask);
1498            continue;
1499        }
1500        let ok = if let Some(cached) = incremental.slice_proofs.get(key) {
1501            cached
1502        } else {
1503            let (mut ok, mut spent) =
1504                safe_slice_subsumes(engine, state, catalog.safe().filtered_trie(), limits);
1505            if ok && body_prefix {
1506                let (extra_ok, extra_spent) =
1507                    slice_subsumes(engine, state, catalog.body_extra().filtered_trie(), limits);
1508                ok = extra_ok;
1509                spent = spent.saturating_add(extra_spent);
1510            }
1511            work = work.saturating_add(spent);
1512            incremental
1513                .slice_proofs
1514                .insert(key, ok, limits.max_slice_cache_bytes);
1515            ok
1516        };
1517        if !ok {
1518            proved = false;
1519            break;
1520        }
1521        if reject_invalid {
1522            let rejection_key = SliceProofKey {
1523                reject_invalid: true,
1524                ..key
1525            };
1526            reject_invalid = if let Some(cached) = incremental.slice_proofs.get(rejection_key) {
1527                cached
1528            } else {
1529                let (rejected, spent) = slice_rejects_at_lexical_error(
1530                    engine,
1531                    state,
1532                    catalog.body_invalid_trie(),
1533                    limits,
1534                );
1535                work = work.saturating_add(spent);
1536                incremental.slice_proofs.insert(
1537                    rejection_key,
1538                    rejected,
1539                    limits.max_slice_cache_bytes,
1540                );
1541                rejected
1542            };
1543        }
1544    }
1545    incremental.certificate_proofs = proofs;
1546    if proved {
1547        incremental
1548            .state
1549            .prepare_certified_slice(&mut ProofContext::with_fuel(max_bytes, certificate_fuel))?;
1550    }
1551    let selected = SelectedSlice {
1552        catalog,
1553        bounded,
1554        body_prefix,
1555        reject_invalid,
1556        candidate,
1557    };
1558    Ok((proved.then_some(selected), work))
1559}
1560
1561/// The safe tokens a mask may take wholesale, plus the trie that still needs an exact walk.
1562struct SelectedSlice<'a> {
1563    catalog: &'a TokenSliceCatalog,
1564    /// `None` means every safe token fits the remaining budgets, so the whole safe mask applies.
1565    bounded: Option<BoundedMask<'a>>,
1566    body_prefix: bool,
1567    reject_invalid: bool,
1568    candidate: Option<Arc<[u64]>>,
1569}
1570
1571fn select_adaptive_trie<'a>(
1572    incremental: &mut IncrementalMatcher,
1573    vocabulary: &'a VocabularyHandle,
1574    limits: StructuredLimits,
1575) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1576    let (body, body_work) = select_wrapped_slice(incremental, vocabulary, limits, true)?;
1577    if body.is_some() {
1578        return Ok((body, body_work));
1579    }
1580    let (slice, work) = select_plain_adaptive_trie(incremental, vocabulary, limits)?;
1581    Ok((slice, work.saturating_add(body_work)))
1582}
1583
1584fn select_plain_adaptive_trie<'a>(
1585    incremental: &mut IncrementalMatcher,
1586    vocabulary: &'a VocabularyHandle,
1587    limits: StructuredLimits,
1588) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1589    if incremental.state.local_slice_residual().is_none() {
1590        return select_wrapped_slice(incremental, vocabulary, limits, false);
1591    }
1592    let (remaining_document_bytes, remaining_scalars, needs_proof) = {
1593        let Some(residual) = incremental.state.local_slice_residual() else {
1594            return Ok((None, 0));
1595        };
1596        let needs_proof = match (residual.engine, residual.pattern_state) {
1597            // No local automaton constrains this position, so nothing can accept and pop here.
1598            (None, None) => false,
1599            (Some(engine), Some(state))
1600                if engine.consume_token(state, b"a").is_some()
1601                    && engine.consume_token(state, b"z").is_some() =>
1602            {
1603                true
1604            }
1605            _ => return Ok((None, 0)),
1606        };
1607        (
1608            residual.remaining_document_bytes,
1609            residual.remaining_scalars,
1610            needs_proof,
1611        )
1612    };
1613    let Some(catalog) = vocabulary.token_slices().map(AsRef::as_ref) else {
1614        return Ok((None, 0));
1615    };
1616    if catalog.safe().scalar_count().is_some() {
1617        return Ok((None, 0));
1618    }
1619    // Length-bounded tables retain safe short tokens when the full mask exceeds a budget.
1620    // Resource overflow remains an error in the exact walker.
1621    if catalog.safe().max_byte_len() > remaining_document_bytes {
1622        return Ok((None, 0));
1623    }
1624    let unbounded =
1625        catalog.safe().max_byte_len() <= remaining_document_bytes && remaining_scalars.is_none();
1626    let (bounded, admitted_bytes) = if unbounded {
1627        (None, catalog.safe().max_byte_len())
1628    } else {
1629        let Some(mask) = catalog.bounded_mask(remaining_document_bytes, remaining_scalars) else {
1630            return Ok((None, 0));
1631        };
1632        (
1633            Some(mask),
1634            remaining_document_bytes
1635                .min(catalog.safe().max_byte_len())
1636                .min(remaining_scalars.map_or(usize::MAX, |scalars| scalars.saturating_mul(4))),
1637        )
1638    };
1639    if incremental.state.prepare_local_slice(admitted_bytes)? == SlicePreparation::Ineligible {
1640        return select_wrapped_slice(incremental, vocabulary, limits, false);
1641    }
1642    let Some(residual) = incremental.state.local_slice_residual() else {
1643        return Ok((None, 0));
1644    };
1645    let selected = SelectedSlice {
1646        catalog,
1647        bounded,
1648        body_prefix: false,
1649        reject_invalid: false,
1650        candidate: None,
1651    };
1652    if !needs_proof {
1653        return Ok((Some(selected), 0));
1654    }
1655    let Some(engine) = residual.engine else {
1656        return Ok((None, 0));
1657    };
1658    let Some(state) = residual.pattern_state else {
1659        return Ok((None, 0));
1660    };
1661    let key = SliceProofKey {
1662        vocabulary: vocabulary.fingerprint(),
1663        node: residual.node,
1664        state,
1665        body_prefix: false,
1666        reject_invalid: false,
1667    };
1668    if let Some(subsumes) = incremental.slice_proofs.get(key) {
1669        return Ok((subsumes.then_some(selected), 0));
1670    }
1671    let (proved, work) = safe_slice_subsumes(engine, state, catalog.safe().filtered_trie(), limits);
1672    incremental
1673        .slice_proofs
1674        .insert(key, proved, limits.max_slice_cache_bytes);
1675    Ok((proved.then_some(selected), work))
1676}
1677
1678fn safe_slice_subsumes(
1679    engine: &crate::automaton::RefEngine,
1680    start: StateId,
1681    trie: &VocabTrie,
1682    limits: StructuredLimits,
1683) -> (bool, u64) {
1684    let (proved, work) = neutral_language_subsumes(engine, start, limits);
1685    if proved {
1686        return (true, work);
1687    }
1688    let mut remaining = limits;
1689    remaining.max_mask_work = remaining.max_mask_work.saturating_sub(work);
1690    let (proved, extra) = slice_subsumes(engine, start, trie, remaining);
1691    (proved, work.saturating_add(extra))
1692}
1693
1694/// A closed product proves all neutral strings without visiting every vocabulary prefix.
1695fn neutral_language_subsumes(
1696    engine: &crate::automaton::RefEngine,
1697    start: StateId,
1698    limits: StructuredLimits,
1699) -> (bool, u64) {
1700    const CAPACITY: usize = 64;
1701    if std::mem::size_of::<[(StateId, u8); CAPACITY]>() > limits.max_mask_scratch_bytes {
1702        return (false, 0);
1703    }
1704    let mut pairs = [(start, 0u8); CAPACITY];
1705    let mut length = 1;
1706    let mut cursor = 0;
1707    let mut work = 0;
1708    while cursor < length {
1709        let (state, mode) = pairs[cursor];
1710        cursor += 1;
1711        for byte in 0..=u8::MAX {
1712            let Some(next_mode) = neutral_utf8_step(mode, byte) else {
1713                continue;
1714            };
1715            if work == limits.max_mask_work {
1716                return (false, work);
1717            }
1718            work += 1;
1719            let Some(next) = engine.consume_token(state, &[byte]) else {
1720                return (false, work);
1721            };
1722            let pair = (next, next_mode);
1723            if !pairs[..length].contains(&pair) {
1724                if length == CAPACITY {
1725                    return (false, work);
1726                }
1727                pairs[length] = pair;
1728                length += 1;
1729            }
1730        }
1731    }
1732    (true, work)
1733}
1734
1735fn neutral_utf8_step(mode: u8, byte: u8) -> Option<u8> {
1736    match (mode, byte) {
1737        (0, b'"' | b'\\') => None,
1738        (0, 0x20..=0x7f) => Some(0),
1739        (0, 0xc2..=0xdf) => Some(1),
1740        (0, 0xe0) => Some(4),
1741        (0, 0xed) => Some(5),
1742        (0, 0xe1..=0xec | 0xee..=0xef) => Some(2),
1743        (0, 0xf0) => Some(6),
1744        (0, 0xf4) => Some(7),
1745        (0, 0xf1..=0xf3) => Some(3),
1746        (1..=3, 0x80..=0xbf) => Some(mode - 1),
1747        (4, 0xa0..=0xbf) | (5, 0x80..=0x9f) => Some(1),
1748        (6, 0x90..=0xbf) | (7, 0x80..=0x8f) => Some(2),
1749        _ => None,
1750    }
1751}
1752
1753/// Proves every trie token has a live transition from `start`.
1754/// Acceptance does not pop a regular frame at a token boundary.
1755fn slice_subsumes(
1756    engine: &crate::automaton::RefEngine,
1757    start: StateId,
1758    trie: &VocabTrie,
1759    limits: StructuredLimits,
1760) -> (bool, u64) {
1761    let frontier = trie.max_dfs_frontier();
1762    let Some(bytes) = frontier.checked_mul(std::mem::size_of::<(TrieNodeId, StateId)>()) else {
1763        return (false, 0);
1764    };
1765    if bytes > limits.max_mask_scratch_bytes {
1766        return (false, 0);
1767    }
1768    let mut stack = Vec::new();
1769    if stack.try_reserve_exact(frontier.max(1)).is_err() {
1770        return (false, 0);
1771    }
1772    stack.push((TrieNodeId(0), start));
1773    let mut work = 0u64;
1774    while let Some((node, state)) = stack.pop() {
1775        let Ok((keys, children)) = trie.children(node) else {
1776            return (false, work);
1777        };
1778        for (&key, &child) in keys.iter().zip(children).rev() {
1779            let Some(next_work) = work.checked_add(1) else {
1780                return (false, limits.max_mask_work);
1781            };
1782            work = next_work;
1783            if work > limits.max_mask_work {
1784                return (false, limits.max_mask_work);
1785            }
1786            let Ok(byte) = u8::try_from(key) else {
1787                return (false, work);
1788            };
1789            let Some(next) = engine.consume_token(state, &[byte]) else {
1790                return (false, work);
1791            };
1792            stack.push((child, next));
1793        }
1794    }
1795    (true, work)
1796}
1797
1798fn candidate_live_mask(
1799    engine: &crate::automaton::RefEngine,
1800    start: StateId,
1801    safe: &VocabTrie,
1802    extra: Option<&VocabTrie>,
1803    width: usize,
1804    limits: StructuredLimits,
1805) -> Option<(Arc<[u64]>, u64)> {
1806    let words = width.div_ceil(u64::BITS as usize);
1807    let bytes = words.checked_mul(std::mem::size_of::<u64>())?;
1808    if bytes > limits.max_slice_cache_bytes / 2 || bytes > limits.max_mask_scratch_bytes {
1809        return None;
1810    }
1811    let mut mask = Vec::new();
1812    mask.try_reserve_exact(words).ok()?;
1813    mask.resize(words, 0);
1814    let mut work = 0;
1815    candidate_live_mask_into(engine, start, safe, &mut mask, &mut work, limits)?;
1816    if let Some(extra) = extra {
1817        candidate_live_mask_into(engine, start, extra, &mut mask, &mut work, limits)?;
1818    }
1819    Some((Arc::from(mask), work))
1820}
1821
1822fn candidate_live_mask_into(
1823    engine: &crate::automaton::RefEngine,
1824    start: StateId,
1825    trie: &VocabTrie,
1826    mask: &mut [u64],
1827    work: &mut u64,
1828    limits: StructuredLimits,
1829) -> Option<()> {
1830    let mut stack = Vec::new();
1831    stack
1832        .try_reserve_exact(trie.max_dfs_frontier().max(1))
1833        .ok()?;
1834    stack.push((TrieNodeId(0), start));
1835    while let Some((node, state)) = stack.pop() {
1836        for &token in trie.leaf_tokens_of(node).ok()? {
1837            let index = usize::try_from(token).ok()?;
1838            *mask.get_mut(index / u64::BITS as usize)? |= 1u64 << (index % u64::BITS as usize);
1839        }
1840        let (keys, children) = trie.children(node).ok()?;
1841        for (&key, &child) in keys.iter().zip(children).rev() {
1842            *work = work.checked_add(1)?;
1843            if *work > limits.max_mask_work {
1844                return None;
1845            }
1846            let byte = u8::try_from(key).ok()?;
1847            let Some(next) = engine.consume_token(state, &[byte]) else {
1848                continue;
1849            };
1850            if !engine.is_dead(next) {
1851                stack.push((child, next));
1852            }
1853        }
1854    }
1855    Some(())
1856}
1857
1858fn slice_rejects_at_lexical_error(
1859    engine: &crate::automaton::RefEngine,
1860    start: StateId,
1861    trie: &VocabTrie,
1862    limits: StructuredLimits,
1863) -> (bool, u64) {
1864    use super::lexer::{DecodeStep, JsonStringDecoder};
1865    let frontier = trie.max_dfs_frontier().max(1);
1866    let Some(bytes) =
1867        frontier.checked_mul(std::mem::size_of::<(TrieNodeId, StateId, JsonStringDecoder)>())
1868    else {
1869        return (false, 0);
1870    };
1871    if bytes > limits.max_mask_scratch_bytes || engine.is_accepting(start) {
1872        return (false, 0);
1873    }
1874    let mut stack = Vec::new();
1875    if stack.try_reserve_exact(frontier).is_err() {
1876        return (false, 0);
1877    }
1878    stack.push((TrieNodeId(0), start, JsonStringDecoder::new()));
1879    let mut work = 0u64;
1880    while let Some((node, state, decoder)) = stack.pop() {
1881        if !trie
1882            .leaf_tokens_of(node)
1883            .is_ok_and(|tokens| tokens.is_empty())
1884        {
1885            return (false, work);
1886        }
1887        let Ok((keys, children)) = trie.children(node) else {
1888            return (false, work);
1889        };
1890        for (&key, &child) in keys.iter().zip(children).rev() {
1891            if work >= limits.max_mask_work {
1892                return (false, work);
1893            }
1894            work += 1;
1895            let Ok(byte) = u8::try_from(key) else {
1896                return (false, work);
1897            };
1898            let mut next_decoder = decoder;
1899            if byte == b'"' && decoder.at_boundary() {
1900                return (false, work);
1901            }
1902            let next = engine.consume_token(state, &[byte]);
1903            if next_decoder.push(byte) == DecodeStep::Invalid {
1904                // Rejection at the same nonaccepting prefix cannot replay into a parent.
1905                if next.is_some() || engine.is_accepting(state) {
1906                    return (false, work);
1907                }
1908                continue;
1909            }
1910            let Some(next) = next.filter(|&state| !engine.is_accepting(state)) else {
1911                return (false, work);
1912            };
1913            stack.push((child, next, next_decoder));
1914        }
1915    }
1916    (true, work)
1917}
1918
1919fn or_token_mask(output: &mut [u8], mask: &[u64], filter: Option<&[u64]>) {
1920    for (index, (chunk, word)) in output.chunks_mut(8).zip(mask).enumerate() {
1921        let word =
1922            *word & filter.map_or(u64::MAX, |filter| filter.get(index).copied().unwrap_or(0));
1923        let bytes = word.to_le_bytes();
1924        for (dst, src) in chunk.iter_mut().zip(bytes) {
1925            *dst |= src;
1926        }
1927    }
1928}
1929
1930/// Like `or_token_mask`, but each word is intersected on the fly, so no scratch buffer is needed.
1931fn or_bounded_mask(output: &mut [u8], mask: BoundedMask<'_>, filter: Option<&[u64]>) {
1932    for (index, chunk) in output.chunks_mut(8).take(mask.words()).enumerate() {
1933        let word = mask.word(index)
1934            & filter.map_or(u64::MAX, |filter| filter.get(index).copied().unwrap_or(0));
1935        let bytes = word.to_le_bytes();
1936        for (dst, src) in chunk.iter_mut().zip(bytes) {
1937            *dst |= src;
1938        }
1939    }
1940}
1941
1942#[cfg(test)]
1943fn walk_full_trie_mask_for_test(
1944    incremental: &mut IncrementalMatcher,
1945    vocabulary: &VocabularyHandle,
1946    bound: &BoundByteTrie,
1947    out: &mut [u8],
1948    limits: StructuredLimits,
1949) -> Result<(), StructuredMatcherError> {
1950    if bound.fingerprint() != vocabulary.fingerprint() {
1951        return Err(missing_trie_error());
1952    }
1953    walk_trie_mask_inner(incremental, vocabulary, bound.trie(), out, limits, &mut 0)
1954}
1955
1956fn reserve_events(
1957    events: &mut Vec<TrieEvent>,
1958    additional: usize,
1959    limits: StructuredLimits,
1960) -> Result<(), StructuredMatcherError> {
1961    reserve_events_with_extra(events, additional, 0, limits)
1962}
1963
1964fn reserve_events_with_extra(
1965    events: &mut Vec<TrieEvent>,
1966    additional: usize,
1967    extra_bytes: usize,
1968    limits: StructuredLimits,
1969) -> Result<(), StructuredMatcherError> {
1970    let needed = events.len().checked_add(additional).ok_or({
1971        StructuredMatcherError::ResourceLimit {
1972            kind: LimitKind::MaskScratchBytes,
1973            observed: usize::MAX,
1974            limit: limits.max_mask_scratch_bytes,
1975        }
1976    })?;
1977    let bytes = needed
1978        .checked_mul(std::mem::size_of::<TrieEvent>())
1979        .and_then(|bytes| bytes.checked_add(extra_bytes))
1980        .ok_or(StructuredMatcherError::ResourceLimit {
1981            kind: LimitKind::MaskScratchBytes,
1982            observed: usize::MAX,
1983            limit: limits.max_mask_scratch_bytes,
1984        })?;
1985    if bytes > limits.max_mask_scratch_bytes {
1986        return Err(StructuredMatcherError::ResourceLimit {
1987            kind: LimitKind::MaskScratchBytes,
1988            observed: bytes,
1989            limit: limits.max_mask_scratch_bytes,
1990        });
1991    }
1992    if needed > events.capacity() {
1993        events
1994            .try_reserve_exact(needed - events.len())
1995            .map_err(|_| StructuredMatcherError::ResourceLimit {
1996                kind: LimitKind::MaskScratchBytes,
1997                observed: bytes,
1998                limit: limits.max_mask_scratch_bytes,
1999            })?;
2000    }
2001    let actual = events
2002        .capacity()
2003        .checked_mul(std::mem::size_of::<TrieEvent>())
2004        .and_then(|bytes| bytes.checked_add(extra_bytes))
2005        .ok_or(StructuredMatcherError::ResourceLimit {
2006            kind: LimitKind::MaskScratchBytes,
2007            observed: usize::MAX,
2008            limit: limits.max_mask_scratch_bytes,
2009        })?;
2010    if actual > limits.max_mask_scratch_bytes {
2011        return Err(StructuredMatcherError::ResourceLimit {
2012            kind: LimitKind::MaskScratchBytes,
2013            observed: actual,
2014            limit: limits.max_mask_scratch_bytes,
2015        });
2016    }
2017    Ok(())
2018}
2019
2020fn reserve_lexical_path(
2021    incremental: &mut IncrementalMatcher,
2022    needed: usize,
2023    limits: StructuredLimits,
2024) -> Result<(), StructuredMatcherError> {
2025    let projected = incremental
2026        .events
2027        .capacity()
2028        .checked_mul(std::mem::size_of::<TrieEvent>())
2029        .and_then(|bytes| bytes.checked_add(needed))
2030        .ok_or(StructuredMatcherError::ResourceLimit {
2031            kind: LimitKind::MaskScratchBytes,
2032            observed: usize::MAX,
2033            limit: limits.max_mask_scratch_bytes,
2034        })?;
2035    if projected > limits.max_mask_scratch_bytes {
2036        return Err(StructuredMatcherError::ResourceLimit {
2037            kind: LimitKind::MaskScratchBytes,
2038            observed: projected,
2039            limit: limits.max_mask_scratch_bytes,
2040        });
2041    }
2042    if needed > incremental.lexical_path.capacity() {
2043        incremental
2044            .lexical_path
2045            .try_reserve_exact(needed - incremental.lexical_path.len())
2046            .map_err(|_| StructuredMatcherError::ResourceLimit {
2047                kind: LimitKind::MaskScratchBytes,
2048                observed: projected,
2049                limit: limits.max_mask_scratch_bytes,
2050            })?;
2051    }
2052    let actual = incremental
2053        .events
2054        .capacity()
2055        .checked_mul(std::mem::size_of::<TrieEvent>())
2056        .and_then(|bytes| bytes.checked_add(incremental.lexical_path.capacity()))
2057        .ok_or(StructuredMatcherError::ResourceLimit {
2058            kind: LimitKind::MaskScratchBytes,
2059            observed: usize::MAX,
2060            limit: limits.max_mask_scratch_bytes,
2061        })?;
2062    if actual > limits.max_mask_scratch_bytes {
2063        return Err(StructuredMatcherError::ResourceLimit {
2064            kind: LimitKind::MaskScratchBytes,
2065            observed: actual,
2066            limit: limits.max_mask_scratch_bytes,
2067        });
2068    }
2069    Ok(())
2070}
2071
2072fn charge_mask_work(
2073    work: &mut u64,
2074    bytes: usize,
2075    ids: usize,
2076    limits: StructuredLimits,
2077) -> Result<(), StructuredMatcherError> {
2078    let delta = u64::try_from(bytes)
2079        .ok()
2080        .and_then(|value| value.checked_add(u64::try_from(ids).ok()?))
2081        .ok_or(StructuredMatcherError::ResourceLimit {
2082            kind: LimitKind::MaskWork,
2083            observed: usize::MAX,
2084            limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2085        })?;
2086    let next = work
2087        .checked_add(delta)
2088        .ok_or(StructuredMatcherError::ResourceLimit {
2089            kind: LimitKind::MaskWork,
2090            observed: usize::MAX,
2091            limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2092        })?;
2093    if next > limits.max_mask_work {
2094        return Err(StructuredMatcherError::ResourceLimit {
2095            kind: LimitKind::MaskWork,
2096            observed: usize::try_from(next).unwrap_or(usize::MAX),
2097            limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2098        });
2099    }
2100    *work = next;
2101    Ok(())
2102}
2103
2104#[cfg(test)]
2105mod tests {
2106    use super::*;
2107    use crate::frontend::schema_to_ir;
2108    use crate::index::TrieCache;
2109    use crate::ir::{Charset, CompileOptions, ScalarLit, MAX_UNROLLED_ENUM};
2110    use crate::vocab::build_vocabulary;
2111    use rustc_hash::FxHashMap;
2112
2113    fn ir(schema: &str) -> Arc<SchemaIR> {
2114        Arc::new(schema_to_ir(schema, CompileOptions::default()).expect("schema"))
2115    }
2116
2117    fn program(schema: &str) -> Arc<StructuredProgram> {
2118        StructuredProgram::compile(ir(schema)).expect("program")
2119    }
2120
2121    fn large_string_enum_ir() -> Arc<SchemaIR> {
2122        let mut builder = crate::ir::Builder::new(CompileOptions::default());
2123        let values = (0..=MAX_UNROLLED_ENUM)
2124            .map(|index| ScalarLit::Str(format!("member-{index:04}")))
2125            .collect();
2126        let root = builder.enum_values(values).expect("enum");
2127        Arc::new(builder.finish(root).expect("ir"))
2128    }
2129
2130    fn large_string_enum_schema() -> String {
2131        let members = (0..=MAX_UNROLLED_ENUM)
2132            .map(|index| format!(r#""member-{index:04}""#))
2133            .collect::<Vec<_>>()
2134            .join(",");
2135        format!(r#"{{"enum":["a","ab","abc","é","😀",{members}]}}"#)
2136    }
2137
2138    fn mask(
2139        matcher: &mut StructuredMatcher,
2140        width: usize,
2141        records: &[(Vec<u8>, Vec<u32>)],
2142    ) -> Bitmask {
2143        matcher
2144            .allowed_mask_from_records(
2145                width,
2146                records
2147                    .iter()
2148                    .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
2149            )
2150            .expect("mask")
2151    }
2152
2153    #[cfg(feature = "bench-internals")]
2154    #[derive(Clone, Copy, Debug)]
2155    enum NearLimitTuning {
2156        Document(usize),
2157        Key(usize),
2158        String(usize),
2159        Session(usize),
2160        Undo(usize),
2161        Canonical(usize),
2162    }
2163
2164    #[cfg(feature = "bench-internals")]
2165    fn tune_near_limit(matcher: &mut StructuredMatcher, tuning: NearLimitTuning) {
2166        let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2167            panic!("near-limit matrix requires the incremental backend")
2168        };
2169        match tuning {
2170            NearLimitTuning::Document(limit) => {
2171                inner.state.set_document_limit_for_test(limit);
2172            }
2173            NearLimitTuning::Key(limit) => inner.state.set_key_limit_for_test(limit),
2174            NearLimitTuning::String(limit) => inner.state.set_string_limit_for_test(limit),
2175            NearLimitTuning::Session(limit) => inner.state.set_session_limit_for_test(limit),
2176            NearLimitTuning::Undo(limit) => inner.state.set_undo_limit_for_test(limit),
2177            NearLimitTuning::Canonical(limit) => {
2178                inner.state.set_unique_canonical_limit_for_test(limit);
2179            }
2180        }
2181    }
2182
2183    #[cfg(feature = "bench-internals")]
2184    fn assert_incremental_accounting(matcher: &StructuredMatcher, label: &str) {
2185        let MatcherBackend::Incremental(inner) = &matcher.backend else {
2186            panic!("{label}: expected incremental matcher")
2187        };
2188        assert_eq!(
2189            matcher.retained_session_bytes(),
2190            inner.state.recomputed_retained_bytes(),
2191            "{label}: retained-byte ledger diverged from actual capacities: {}",
2192            inner.state.accounting_breakdown_for_test()
2193        );
2194    }
2195
2196    #[cfg(feature = "bench-internals")]
2197    fn assert_three_route_mask_and_advance(
2198        program: &Arc<StructuredProgram>,
2199        vocabulary: &VocabularyHandle,
2200        trie: &BoundByteTrie,
2201        prefix: &[u8],
2202        label: &str,
2203    ) -> (Vec<u8>, u64) {
2204        let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
2205        let mut adaptive = program.new_matcher().expect("adaptive matcher");
2206        let mut full = program.new_matcher().expect("full matcher");
2207        let mut record = program.new_matcher().expect("record matcher");
2208        for matcher in [&mut adaptive, &mut full, &mut record] {
2209            if !prefix.is_empty() {
2210                assert!(matcher.advance(prefix).unwrap(), "{label}: invalid prefix");
2211            }
2212        }
2213        let before = (
2214            adaptive.is_accepting(),
2215            adaptive.is_dead(),
2216            adaptive.eos_legal(),
2217        );
2218        let mut adaptive_out = vec![0xa5; output_len];
2219        let mut full_out = vec![0xa5; output_len];
2220        let mut record_out = vec![0xa5; output_len];
2221        adaptive
2222            .write_mask_le_bytes_into(vocabulary, Some(trie), &mut adaptive_out)
2223            .unwrap_or_else(|error| panic!("{label}: adaptive mask: {error}"));
2224        let nodes = adaptive.mask_work_metrics_for_bench().trie_nodes;
2225        full.write_full_trie_mask_for_bench(vocabulary, trie, &mut full_out)
2226            .unwrap_or_else(|error| panic!("{label}: full mask: {error}"));
2227        record
2228            .write_record_mask_le_bytes_into(
2229                vocabulary.mask_vocab_size(),
2230                vocabulary.iter_records(),
2231                &mut record_out,
2232            )
2233            .unwrap_or_else(|error| panic!("{label}: record mask: {error}"));
2234        assert_mask_bytes_equal(&adaptive_out, &full_out, vocabulary, label);
2235        assert_mask_bytes_equal(&adaptive_out, &record_out, vocabulary, label);
2236        assert_eq!(
2237            (
2238                adaptive.is_accepting(),
2239                adaptive.is_dead(),
2240                adaptive.eos_legal(),
2241            ),
2242            before,
2243            "{label}: mask changed committed logical state"
2244        );
2245        assert_incremental_accounting(&adaptive, label);
2246        assert_incremental_accounting(&full, label);
2247        assert_incremental_accounting(&record, label);
2248        let retained = adaptive.retained_session_bytes();
2249        let mut repeated = vec![0xa5; output_len];
2250        adaptive
2251            .write_mask_le_bytes_into(vocabulary, Some(trie), &mut repeated)
2252            .unwrap_or_else(|error| panic!("{label}: repeated mask: {error}"));
2253        assert_eq!(repeated, adaptive_out, "{label}: repeated mask drift");
2254        assert_eq!(
2255            adaptive.retained_session_bytes(),
2256            retained,
2257            "{label}: repeated mask retained additional capacity"
2258        );
2259        assert_incremental_accounting(&adaptive, label);
2260
2261        for (token, ids) in vocabulary.iter_records() {
2262            let masked = ids
2263                .first()
2264                .is_some_and(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0);
2265            assert!(
2266                ids.iter().all(|id| {
2267                    (adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0) == masked
2268                }),
2269                "{label}: alias bits disagree for {token:?}"
2270            );
2271            let mut equivalent = program.new_matcher().expect("equivalent matcher");
2272            if !prefix.is_empty() {
2273                assert!(
2274                    equivalent.advance(prefix).unwrap(),
2275                    "{label}: replay prefix"
2276                );
2277            }
2278            let before_transition = (
2279                equivalent.is_accepting(),
2280                equivalent.is_dead(),
2281                equivalent.eos_legal(),
2282            );
2283            let accepted = equivalent
2284                .advance(token)
2285                .unwrap_or_else(|error| panic!("{label}: token transition error: {error}"));
2286            assert_eq!(
2287                masked, accepted,
2288                "{label}: mask/advance disagreement for {token:?}"
2289            );
2290            if !accepted {
2291                assert_eq!(
2292                    (
2293                        equivalent.is_accepting(),
2294                        equivalent.is_dead(),
2295                        equivalent.eos_legal(),
2296                    ),
2297                    before_transition,
2298                    "{label}: rejected transition mutated committed state for {token:?}"
2299                );
2300            }
2301            assert_incremental_accounting(&equivalent, label);
2302        }
2303        (adaptive_out, nodes)
2304    }
2305
2306    #[cfg(feature = "bench-internals")]
2307    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
2308    enum CompletionSearch {
2309        Found,
2310        ProvenTrap,
2311        Inconclusive,
2312    }
2313
2314    /// Test-only independent liveness search. It rebuilds the matcher for every node, prioritizes
2315    /// JSON closers, and treats both depth and node-budget exhaustion as inconclusive.
2316    #[cfg(feature = "bench-internals")]
2317    fn closer_first_completion_search(
2318        program: &Arc<StructuredProgram>,
2319        prefix: &[u8],
2320        depth: usize,
2321        remaining_nodes: &mut usize,
2322    ) -> CompletionSearch {
2323        let mut matcher = program.new_matcher().expect("liveness matcher");
2324        if !matcher.advance(prefix).expect("liveness advance") {
2325            return CompletionSearch::ProvenTrap;
2326        }
2327        if matcher.is_accepting() {
2328            return CompletionSearch::Found;
2329        }
2330        if depth == 0 || *remaining_nodes == 0 {
2331            return CompletionSearch::Inconclusive;
2332        }
2333        const CLOSERS_FIRST: &[u8] =
2334            b"}\"],:0123456789truefalsnul abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-/\\.";
2335        let mut alphabet = Vec::with_capacity(98);
2336        for byte in CLOSERS_FIRST
2337            .iter()
2338            .copied()
2339            .chain(0x20u8..=0x7e)
2340            .chain([b'\t', b'\n'])
2341        {
2342            if !alphabet.contains(&byte) {
2343                alphabet.push(byte);
2344            }
2345        }
2346        let mut saw_inconclusive = false;
2347        for byte in alphabet {
2348            if *remaining_nodes == 0 {
2349                return CompletionSearch::Inconclusive;
2350            }
2351            *remaining_nodes -= 1;
2352            let mut candidate = Vec::with_capacity(prefix.len() + 1);
2353            candidate.extend_from_slice(prefix);
2354            candidate.push(byte);
2355            let mut probe = program.new_matcher().expect("liveness probe");
2356            if !probe.advance(&candidate).expect("liveness candidate") {
2357                continue;
2358            }
2359            match closer_first_completion_search(program, &candidate, depth - 1, remaining_nodes) {
2360                CompletionSearch::Found => return CompletionSearch::Found,
2361                CompletionSearch::Inconclusive => saw_inconclusive = true,
2362                CompletionSearch::ProvenTrap => {}
2363            }
2364        }
2365        if saw_inconclusive {
2366            CompletionSearch::Inconclusive
2367        } else {
2368            CompletionSearch::ProvenTrap
2369        }
2370    }
2371
2372    #[cfg(feature = "bench-internals")]
2373    fn assert_near_limit_three_routes(
2374        program: &Arc<StructuredProgram>,
2375        vocabulary: &VocabularyHandle,
2376        trie: &BoundByteTrie,
2377        prefix: &[u8],
2378        tuning: NearLimitTuning,
2379        label: &str,
2380    ) {
2381        let mask_bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
2382        let mut adaptive = program.new_matcher().expect("adaptive matcher");
2383        let mut full = program.new_matcher().expect("full matcher");
2384        let mut records = program.new_matcher().expect("record matcher");
2385        for matcher in [&mut adaptive, &mut full, &mut records] {
2386            assert!(matcher.advance(prefix).unwrap(), "{label}: prefix");
2387            tune_near_limit(matcher, tuning);
2388        }
2389        let before = (
2390            adaptive.is_accepting(),
2391            adaptive.is_dead(),
2392            adaptive.eos_legal(),
2393        );
2394        let mut adaptive_out = vec![0xa5; mask_bytes];
2395        let mut full_out = vec![0; mask_bytes];
2396        let mut record_out = vec![0xa5; mask_bytes];
2397        let adaptive_result =
2398            adaptive.write_mask_le_bytes_into(vocabulary, Some(trie), &mut adaptive_out);
2399        let full_result = {
2400            let MatcherBackend::Incremental(inner) = &mut full.backend else {
2401                panic!("{label}: incremental full matcher")
2402            };
2403            select_adaptive_trie(inner, vocabulary, program.limits)
2404                .map(|_| ())
2405                .and_then(|()| {
2406                    walk_full_trie_mask_for_test(
2407                        inner,
2408                        vocabulary,
2409                        trie,
2410                        &mut full_out,
2411                        program.limits,
2412                    )
2413                })
2414        };
2415        let record_preparation = {
2416            let MatcherBackend::Incremental(inner) = &mut records.backend else {
2417                panic!("{label}: incremental record matcher")
2418            };
2419            select_adaptive_trie(inner, vocabulary, program.limits).map(|_| ())
2420        };
2421        if record_preparation.is_err() {
2422            record_out.fill(0);
2423        }
2424        let record_result = record_preparation.and_then(|()| {
2425            records.write_record_mask_le_bytes_into(
2426                vocabulary.mask_vocab_size(),
2427                vocabulary.iter_records(),
2428                &mut record_out,
2429            )
2430        });
2431        assert_eq!(
2432            adaptive_result.is_ok(),
2433            full_result.is_ok(),
2434            "{label}: adaptive/full result"
2435        );
2436        assert_eq!(
2437            adaptive_result.is_ok(),
2438            record_result.is_ok(),
2439            "{label}: adaptive/record result"
2440        );
2441        assert_incremental_accounting(&adaptive, &format!("{label}/adaptive"));
2442        assert_incremental_accounting(&full, &format!("{label}/full"));
2443        assert_incremental_accounting(&records, &format!("{label}/record"));
2444        assert_eq!(
2445            (
2446                adaptive.is_accepting(),
2447                adaptive.is_dead(),
2448                adaptive.eos_legal()
2449            ),
2450            before,
2451            "{label}: mask changed committed logical state"
2452        );
2453
2454        if adaptive_result.is_err() {
2455            assert_eq!(adaptive_out, vec![0; mask_bytes], "{label}: adaptive error");
2456            assert_eq!(record_out, vec![0; mask_bytes], "{label}: record error");
2457            let mut retry = vec![0xa5; mask_bytes];
2458            assert!(
2459                adaptive
2460                    .write_mask_le_bytes_into(vocabulary, Some(trie), &mut retry)
2461                    .is_err(),
2462                "{label}: retry must reproduce the resource error"
2463            );
2464            assert_eq!(retry, vec![0; mask_bytes], "{label}: retry error");
2465            assert_incremental_accounting(&adaptive, label);
2466            return;
2467        }
2468
2469        assert_mask_bytes_equal(&adaptive_out, &full_out, vocabulary, label);
2470        assert_mask_bytes_equal(&adaptive_out, &record_out, vocabulary, label);
2471        let retained = adaptive.retained_session_bytes();
2472        let mut repeated = vec![0xa5; mask_bytes];
2473        adaptive
2474            .write_mask_le_bytes_into(vocabulary, Some(trie), &mut repeated)
2475            .unwrap_or_else(|error| {
2476                panic!("{label}: repeated mask failed after retaining {retained} bytes: {error}")
2477            });
2478        assert_eq!(adaptive_out, repeated, "{label}: repeated mask drift");
2479        assert_eq!(
2480            adaptive.retained_session_bytes(),
2481            retained,
2482            "{label}: repeated mask retained more capacity"
2483        );
2484        assert_incremental_accounting(&adaptive, label);
2485
2486        for (token, ids) in vocabulary.iter_records() {
2487            if !ids
2488                .iter()
2489                .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
2490            {
2491                continue;
2492            }
2493            let mut equivalent = program.new_matcher().expect("equivalent matcher");
2494            assert!(
2495                equivalent.advance(prefix).unwrap(),
2496                "{label}: equivalent prefix"
2497            );
2498            tune_near_limit(&mut equivalent, tuning);
2499            let mut equivalent_out = vec![0xa5; mask_bytes];
2500            equivalent
2501                .write_mask_le_bytes_into(vocabulary, Some(trie), &mut equivalent_out)
2502                .unwrap_or_else(|error| panic!("{label}: equivalent mask failed: {error}"));
2503            assert_eq!(equivalent_out, adaptive_out, "{label}: equivalent mask");
2504            assert!(
2505                equivalent.advance(token).unwrap_or_else(|error| {
2506                    panic!("{label}: emitted token raised during advance: {error}")
2507                }),
2508                "{label}: emitted token was rejected during advance"
2509            );
2510            assert_incremental_accounting(&equivalent, label);
2511        }
2512    }
2513
2514    #[cfg(feature = "bench-internals")]
2515    fn adaptive_mask_succeeds_at(
2516        program: &Arc<StructuredProgram>,
2517        vocabulary: &VocabularyHandle,
2518        trie: &BoundByteTrie,
2519        prefix: &[u8],
2520        tuning: NearLimitTuning,
2521    ) -> bool {
2522        let mut matcher = program.new_matcher().expect("limit probe matcher");
2523        assert!(matcher.advance(prefix).unwrap());
2524        tune_near_limit(&mut matcher, tuning);
2525        let mut out = vec![0xa5; vocabulary.mask_vocab_size().div_ceil(32) * 4];
2526        let result = matcher.write_mask_le_bytes_into(vocabulary, Some(trie), &mut out);
2527        if result.is_err() {
2528            assert_eq!(out, vec![0; out.len()]);
2529        }
2530        assert_incremental_accounting(&matcher, "limit probe");
2531        result.is_ok()
2532    }
2533
2534    #[cfg(feature = "bench-internals")]
2535    fn minimum_successful_limit(
2536        program: &Arc<StructuredProgram>,
2537        vocabulary: &VocabularyHandle,
2538        trie: &BoundByteTrie,
2539        prefix: &[u8],
2540        high: usize,
2541        tuning: fn(usize) -> NearLimitTuning,
2542    ) -> usize {
2543        assert!(adaptive_mask_succeeds_at(
2544            program,
2545            vocabulary,
2546            trie,
2547            prefix,
2548            tuning(high)
2549        ));
2550        let mut lo = 0usize;
2551        let mut hi = high;
2552        while lo < hi {
2553            let mid = lo + (hi - lo) / 2;
2554            if adaptive_mask_succeeds_at(program, vocabulary, trie, prefix, tuning(mid)) {
2555                hi = mid;
2556            } else {
2557                lo = mid + 1;
2558            }
2559        }
2560        lo
2561    }
2562
2563    #[cfg(feature = "bench-internals")]
2564    fn around(limit: usize) -> [usize; 3] {
2565        [limit.saturating_sub(1), limit, limit.saturating_add(1)]
2566    }
2567
2568    #[test]
2569    fn neutral_product_proof_is_bounded_and_preserves_constrained_fallback() {
2570        for (schema, expected) in [
2571            (
2572                r#"{"type":"object","properties":{"s":{"type":"string"}}}"#,
2573                true,
2574            ),
2575            (
2576                r#"{"type":"object","properties":{"s":{"type":"string","pattern":"^[a-z]*$"}}}"#,
2577                false,
2578            ),
2579        ] {
2580            let program = program(schema);
2581            let mut matcher = program.new_matcher().unwrap();
2582            assert!(matcher.advance(br#"{"s":"a"#).unwrap());
2583            #[cfg(not(feature = "bench-internals"))]
2584            let MatcherBackend::Incremental(inner) = &matcher.backend;
2585            #[cfg(feature = "bench-internals")]
2586            let inner = match &matcher.backend {
2587                MatcherBackend::Incremental(inner) => inner,
2588                MatcherBackend::Reference(_) => panic!("incremental program"),
2589            };
2590            let residual = inner.state.local_slice_residual().unwrap();
2591            let engine = residual.engine.unwrap();
2592            let state = residual.pattern_state.unwrap();
2593            let (proved, work) = neutral_language_subsumes(engine, state, program.limits);
2594            assert_eq!(proved, expected);
2595            assert!(work > 0 && work < 64 * 256);
2596            let mut limits = program.limits;
2597            limits.max_mask_work = 1;
2598            assert_eq!(neutral_language_subsumes(engine, state, limits), (false, 1));
2599        }
2600    }
2601
2602    #[cfg(feature = "bench-internals")]
2603    #[test]
2604    fn long_payment_key_mask_prepares_every_emitted_token_for_advance() {
2605        let schema = serde_json::json!({
2606            "type": "object", "unevaluatedProperties": false,
2607            "required": ["rail", "amountMinor", "currency", "approvals", "allocation"],
2608            "properties": {
2609                "rail": {"enum": ["ach", "wire", "internal"]},
2610                "amountMinor": {"type": "integer", "multipleOf": 100, "minimum": 100, "maximum": 900},
2611                "currency": {"enum": ["USD", "EUR", "RUB"], "not": {"const": "RUB"}},
2612                "iban": {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"},
2613                "approvals": {"type": "array", "items": {"enum": ["treasury", "ops", "risk"]},
2614                    "contains": {"const": "treasury"}, "uniqueItems": true, "minItems": 1, "maxItems": 3},
2615                "allocation": {"$ref": "#/$defs/split"}
2616            },
2617            "if": {"properties": {"rail": {"const": "wire"}}, "required": ["rail"]},
2618            "then": {"required": ["iban"]}, "else": {"not": {"required": ["iban"]}},
2619            "$defs": {"split": {"$dynamicAnchor": "split", "type": "object", "additionalProperties": false,
2620                "required": ["pct"], "properties": {"pct": {"type": "integer", "minimum": 1, "maximum": 99},
2621                    "sub": {"$dynamicRef": "#split"}}}}
2622        });
2623        let tokens = FxHashMap::from_iter([
2624            (b" .".repeat(300), vec![0, 1]),
2625            (b"a".to_vec(), vec![2]),
2626            (b"\"".to_vec(), vec![3]),
2627            (vec![0xf0, 0x9f], vec![4]),
2628        ]);
2629        let vocabulary =
2630            VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
2631        let trie = TrieCache::new().bind(&vocabulary).unwrap();
2632        let program = program(&schema.to_string());
2633        for (bytes, ids) in vocabulary.iter_records() {
2634            let mut matcher = program.new_matcher().unwrap();
2635            assert!(matcher.advance(br#"{""#).unwrap());
2636            let mut fast = [0; 4];
2637            matcher
2638                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
2639                .unwrap();
2640            let retained_after_cold = matcher.retained_session_bytes();
2641            let mut repeated = [0; 4];
2642            matcher
2643                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut repeated)
2644                .unwrap();
2645            assert_eq!(fast, repeated, "repeated mask changed token bits");
2646            assert_eq!(
2647                matcher.retained_session_bytes(),
2648                retained_after_cold,
2649                "repeated mask retained another key capacity"
2650            );
2651            let MatcherBackend::Incremental(inner) = &matcher.backend else {
2652                panic!("incremental payment program")
2653            };
2654            assert_eq!(
2655                inner.state.retained_bytes(),
2656                inner.state.recomputed_retained_bytes()
2657            );
2658            assert!(
2659                matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64
2660            );
2661            let mut full = [0; 4];
2662            let mut scan = [0; 4];
2663            let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2664                panic!("incremental payment program")
2665            };
2666            walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
2667                .unwrap();
2668            matcher
2669                .write_record_mask_le_bytes_into(
2670                    vocabulary.mask_vocab_size(),
2671                    vocabulary.iter_records(),
2672                    &mut scan,
2673                )
2674                .unwrap();
2675            assert_eq!(fast, full);
2676            assert_eq!(fast, scan);
2677            for (candidate, candidate_ids) in vocabulary.iter_records() {
2678                if !candidate_ids
2679                    .iter()
2680                    .any(|id| fast[*id as usize / 8] & (1 << (*id % 8)) != 0)
2681                {
2682                    continue;
2683                }
2684                let mut equivalent = program.new_matcher().unwrap();
2685                assert!(equivalent.advance(br#"{""#).unwrap());
2686                let mut equivalent_mask = [0; 4];
2687                equivalent
2688                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut equivalent_mask)
2689                    .unwrap();
2690                assert_eq!(equivalent_mask, fast);
2691                assert!(
2692                    equivalent.advance(candidate).unwrap(),
2693                    "emitted token {:?} must advance on an equivalent matcher",
2694                    candidate_ids
2695                );
2696            }
2697            for &id in ids {
2698                if fast[id as usize / 8] & (1 << (id % 8)) != 0 {
2699                    assert!(matcher.advance(bytes).unwrap(), "token {id}");
2700                    break;
2701                }
2702            }
2703        }
2704    }
2705
2706    #[cfg(feature = "bench-internals")]
2707    #[test]
2708    fn key_capacity_lease_transfers_releases_and_rearms_for_a_second_key() {
2709        let long = b" .".repeat(300);
2710        let tokens = FxHashMap::from_iter([
2711            (long.clone(), vec![0]),
2712            (b"a".to_vec(), vec![1]),
2713            (b"\"".to_vec(), vec![2]),
2714            (b":".to_vec(), vec![3]),
2715        ]);
2716        let vocabulary =
2717            VocabularyHandle::new(Arc::new(build_vocabulary(4, tokens).unwrap())).unwrap();
2718        let trie = TrieCache::new().bind(&vocabulary).unwrap();
2719        let program = program(r#"{"type":"object","additionalProperties":true}"#);
2720        let mut matcher = program.new_matcher().unwrap();
2721        assert!(matcher.advance(br#"{""#).unwrap());
2722
2723        let mut adaptive = [0; 4];
2724        matcher
2725            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2726            .unwrap();
2727        let (phase, capacity, recomputed) = match &matcher.backend {
2728            MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2729            MatcherBackend::Reference(_) => panic!("incremental program"),
2730        };
2731        assert_eq!(phase, 1, "cold key mask arms a prepared lease");
2732        assert!(capacity >= long.len());
2733        assert_eq!(matcher.retained_session_bytes(), recomputed);
2734
2735        let mut full = [0; 4];
2736        let mut records = [0; 4];
2737        let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2738            panic!("incremental program")
2739        };
2740        walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
2741        matcher
2742            .write_record_mask_le_bytes_into(
2743                vocabulary.mask_vocab_size(),
2744                vocabulary.iter_records(),
2745                &mut records,
2746            )
2747            .unwrap();
2748        assert_eq!(adaptive, full);
2749        assert_eq!(adaptive, records);
2750
2751        assert!(
2752            matcher.advance(b"a").unwrap(),
2753            "short token uses long lease"
2754        );
2755        let (phase, _, recomputed) = match &matcher.backend {
2756            MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2757            MatcherBackend::Reference(_) => panic!("incremental program"),
2758        };
2759        assert_eq!(phase, 2, "successful commit transfers the lease");
2760        assert_eq!(matcher.retained_session_bytes(), recomputed);
2761        adaptive.fill(0);
2762        matcher
2763            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2764            .unwrap();
2765        let (phase, _, recomputed) = match &matcher.backend {
2766            MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2767            MatcherBackend::Reference(_) => panic!("incremental program"),
2768        };
2769        assert_eq!(
2770            phase, 1,
2771            "a later mask must rearm when the committed prefix increases required capacity"
2772        );
2773        assert_eq!(matcher.retained_session_bytes(), recomputed);
2774        assert!(matcher.advance(b"\"").unwrap());
2775        assert!(matcher.advance(b":").unwrap());
2776        let (phase, capacity, recomputed) = match &matcher.backend {
2777            MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2778            MatcherBackend::Reference(_) => panic!("incremental program"),
2779        };
2780        assert_eq!(phase, 0, "resolved key releases its transferred lease");
2781        assert_eq!(capacity, 0, "maximum token capacity is not retained");
2782        assert_eq!(matcher.retained_session_bytes(), recomputed);
2783
2784        assert!(matcher.advance(br#"0,""#).unwrap());
2785        adaptive.fill(0);
2786        matcher
2787            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2788            .unwrap();
2789        let (phase, capacity, recomputed) = match &matcher.backend {
2790            MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2791            MatcherBackend::Reference(_) => panic!("incremental program"),
2792        };
2793        assert_eq!(phase, 1, "second key receives a fresh lease generation");
2794        assert!(capacity >= long.len());
2795        assert_eq!(matcher.retained_session_bytes(), recomputed);
2796    }
2797
2798    #[cfg(feature = "bench-internals")]
2799    #[test]
2800    fn wrapper_key_capacity_leases_are_distinct_per_structured_cursor() {
2801        let schema = r#"{"anyOf":[
2802            {"type":"object","properties":{"a":{"type":"integer"}}},
2803            {"type":"object","properties":{"b":{"type":"string"}}}
2804        ]}"#;
2805        let long = b"x".repeat(600);
2806        let vocabulary = VocabularyHandle::new(Arc::new(
2807            build_vocabulary(
2808                3,
2809                FxHashMap::from_iter([
2810                    (long, vec![0]),
2811                    (b"a".to_vec(), vec![1]),
2812                    (b"\"".to_vec(), vec![2]),
2813                ]),
2814            )
2815            .unwrap(),
2816        ))
2817        .unwrap();
2818        let trie = TrieCache::new().bind(&vocabulary).unwrap();
2819        let program = program(schema);
2820        let mut matcher = program.new_matcher().unwrap();
2821        assert!(matcher.advance(br#"{""#).unwrap());
2822        let mut out = [0; 4];
2823        matcher
2824            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut out)
2825            .unwrap();
2826        let mut full = [0; 4];
2827        let mut records = [0; 4];
2828        let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2829            panic!("incremental program")
2830        };
2831        walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
2832        matcher
2833            .write_record_mask_le_bytes_into(
2834                vocabulary.mask_vocab_size(),
2835                vocabulary.iter_records(),
2836                &mut records,
2837            )
2838            .unwrap();
2839        assert_eq!(out, full);
2840        assert_eq!(out, records);
2841        for (candidate, ids) in vocabulary.iter_records() {
2842            if !ids
2843                .iter()
2844                .any(|id| out[*id as usize / 8] & (1 << (*id % 8)) != 0)
2845            {
2846                continue;
2847            }
2848            let mut equivalent = program.new_matcher().unwrap();
2849            assert!(equivalent.advance(br#"{""#).unwrap());
2850            let mut equivalent_mask = [0; 4];
2851            equivalent
2852                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut equivalent_mask)
2853                .unwrap();
2854            assert_eq!(equivalent_mask, out);
2855            assert!(equivalent.advance(candidate).unwrap());
2856        }
2857        out.fill(0);
2858        matcher
2859            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut out)
2860            .unwrap();
2861        let (owners, capacity) = match &matcher.backend {
2862            MatcherBackend::Incremental(inner) => inner.state.key_lease_tree_debug(),
2863            MatcherBackend::Reference(_) => panic!("incremental program"),
2864        };
2865        assert!(
2866            owners >= 2,
2867            "each simultaneously stepped wrapper cursor owns a lease"
2868        );
2869        assert!(capacity >= 2 * 600);
2870        let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2871            panic!("incremental program")
2872        };
2873        inner.state.discard_prepared_key_leases_tree();
2874        assert_eq!(
2875            inner.state.key_lease_tree_debug(),
2876            (0, 0),
2877            "recursive cleanup releases every cursor-owned lease"
2878        );
2879    }
2880
2881    #[test]
2882    fn neutral_utf8_product_matches_scalar_boundaries() {
2883        for scalar in (0..=0x10ffff).filter_map(char::from_u32) {
2884            let mut buffer = [0; 4];
2885            let bytes = scalar.encode_utf8(&mut buffer).as_bytes();
2886            let result = bytes
2887                .iter()
2888                .try_fold(0, |mode, &byte| neutral_utf8_step(mode, byte));
2889            assert_eq!(
2890                result,
2891                (scalar >= ' ' && scalar != '"' && scalar != '\\').then_some(0)
2892            );
2893        }
2894        for bytes in [
2895            &[0xc0, 0x80][..],
2896            &[0xed, 0xa0, 0x80],
2897            &[0xf4, 0x90, 0x80, 0x80],
2898            &[0x80],
2899        ] {
2900            assert!(bytes
2901                .iter()
2902                .try_fold(0, |mode, &byte| neutral_utf8_step(mode, byte))
2903                .is_none());
2904        }
2905    }
2906
2907    #[test]
2908    fn supported_structured_schema_uses_incremental_backend() {
2909        let structured = program(
2910            r#"{"type":"object","properties":{"x":{"type":"integer"}},"additionalProperties":true}"#,
2911        );
2912        assert_eq!(structured.backend_kind(), "incremental");
2913        let mut matcher = structured.new_matcher().expect("state");
2914        assert!(matcher.advance(br#"{"#).expect("advance"));
2915        assert!(!matcher.is_dead());
2916    }
2917
2918    #[test]
2919    fn large_minimum_array_inside_an_object_routes_around_the_shuffle_cap() {
2920        let schema = r#"{"type":"object","properties":{"a":{"type":"boolean"},"b":{"type":"array","items":{"type":"null"},"minItems":500}}}"#;
2921        let started = std::time::Instant::now();
2922        let program = program(schema);
2923        assert!(
2924            started.elapsed() < std::time::Duration::from_secs(1),
2925            "structured routing must avoid constructing the oversized shuffle automaton"
2926        );
2927        assert!(program.uses_incremental_backend());
2928
2929        let items = std::iter::repeat_n("null", 500)
2930            .collect::<Vec<_>>()
2931            .join(",");
2932        let document = format!(r#"{{"a":true,"b":[{items}]}}"#);
2933        let mut matcher = program.new_matcher().expect("state");
2934        assert!(matcher.advance(document.as_bytes()).expect("document"));
2935        assert!(matcher.is_accepting());
2936
2937        let too_short = format!(r#"{{"a":true,"b":[{}]}}"#, items.replacen(",null", "", 1));
2938        let mut matcher = program.new_matcher().expect("state");
2939        assert!(!matcher.advance(too_short.as_bytes()).expect("document"));
2940        assert!(!matcher.is_accepting());
2941    }
2942
2943    #[test]
2944    fn direct_shuffle_graph_keeps_the_closed_object_regular() {
2945        let schema = r#"{
2946            "type":"object",
2947            "properties":{
2948                "default":{"type":"string"},
2949                "field":{"enum":["year","organization","owners"]},
2950                "transform":{"enum":["upper"]}
2951            },
2952            "required":["field"],
2953            "additionalProperties":false
2954        }"#;
2955        let program = program(schema);
2956        #[cfg(not(feature = "bench-internals"))]
2957        let ProgramBackend::Incremental(plan) = &program.backend;
2958        #[cfg(feature = "bench-internals")]
2959        let plan = match &program.backend {
2960            ProgramBackend::Incremental(plan) => plan,
2961            ProgramBackend::ReferenceBench => panic!("production program must be incremental"),
2962        };
2963        assert!(matches!(
2964            plan.node(plan.root),
2965            crate::structured::plan::NodePlan::Regular(_)
2966        ));
2967
2968        for document in [
2969            br#"{"field":"year"}"#.as_slice(),
2970            br#"{"transform":"upper","field":"owners","default":"x"}"#,
2971        ] {
2972            let mut matcher = program.new_matcher().expect("state");
2973            assert!(matcher.advance(document).expect("document"));
2974            assert!(matcher.is_accepting());
2975        }
2976    }
2977
2978    #[test]
2979    fn negation_and_lowered_conditionals_use_the_incremental_backend() {
2980        type BackendCase<'a> = (&'a str, &'a [(&'a [u8], bool)]);
2981        let cases: &[BackendCase<'_>] = &[
2982            (
2983                r#"{"not":{"const":1}}"#,
2984                &[(b"1", false), (b"12", true), (b"2", true)],
2985            ),
2986            (
2987                r#"{"if":{"type":"integer"},"then":{"minimum":2},"else":{"type":"string"}}"#,
2988                &[
2989                    (b"1", false),
2990                    (b"2", true),
2991                    (br#""x""#, true),
2992                    (b"true", false),
2993                ],
2994            ),
2995        ];
2996        for (schema, documents) in cases {
2997            let program = program(schema);
2998            assert!(program.uses_incremental_backend(), "{schema}");
2999            for &(document, accepted) in *documents {
3000                let mut matcher = program.new_matcher().expect("state");
3001                let advanced = matcher.advance(document).expect("advance");
3002                assert!(advanced || !accepted, "{schema}: {document:?}");
3003                assert_eq!(matcher.is_accepting(), accepted, "{schema}: {document:?}");
3004            }
3005        }
3006    }
3007
3008    #[test]
3009    fn local_recursive_root_reference_uses_incremental_backend() {
3010        let schema = r##"{
3011            "$defs": {
3012                "node": {
3013                    "type":"object",
3014                    "properties":{"next":{"$ref":"#/$defs/node"}},
3015                    "additionalProperties":false
3016                }
3017            },
3018            "$ref":"#/$defs/node"
3019        }"##;
3020        let program = program(schema);
3021        assert!(program.uses_incremental_backend());
3022        for document in [
3023            b"{}".as_slice(),
3024            br#"{"next":{}}"#,
3025            br#"{"next":{"next":{}}}"#,
3026        ] {
3027            let mut matcher = program.new_matcher().expect("state");
3028            assert!(matcher.advance(document).expect("document"), "{document:?}");
3029            assert!(matcher.is_accepting(), "{document:?}");
3030        }
3031        for document in [br#"{"next":1}"#.as_slice(), br#"{"next":{"extra":true}}"#] {
3032            let mut matcher = program.new_matcher().expect("state");
3033            assert!(
3034                !matcher.advance(document).expect("document"),
3035                "{document:?}"
3036            );
3037            assert!(!matcher.is_accepting(), "{document:?}");
3038        }
3039    }
3040
3041    #[test]
3042    fn recursive_array_anchor_and_shared_definition_refs_are_incremental() {
3043        let cases: &[(&str, &[u8])] = &[
3044            (
3045                r##"{
3046                    "$defs":{"node":{"type":"object","properties":{"children":{"type":"array","items":{"$ref":"#/$defs/node"}}},"additionalProperties":false}},
3047                    "$ref":"#/$defs/node"
3048                }"##,
3049                br#"{"children":[{}, {"children":[]}]}"#,
3050            ),
3051            (
3052                r##"{
3053                    "$defs":{"node":{"$anchor":"node","type":"object","properties":{"next":{"$ref":"#node"}},"additionalProperties":false}},
3054                    "$ref":"#node"
3055                }"##,
3056                br#"{"next":{}}"#,
3057            ),
3058            (
3059                r##"{
3060                    "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3061                    "type":"object",
3062                    "properties":{"left":{"$ref":"#/$defs/node"},"right":{"$ref":"#/$defs/node"}},
3063                    "required":["left","right"],
3064                    "additionalProperties":false
3065                }"##,
3066                br#"{"left":{"next":{}},"right":{}}"#,
3067            ),
3068        ];
3069        for (schema, document) in cases {
3070            let program = program(schema);
3071            assert!(program.uses_incremental_backend(), "{schema}");
3072            let mut matcher = program.new_matcher().expect("state");
3073            assert!(matcher.advance(document).expect("document"), "{document:?}");
3074            assert!(matcher.is_accepting(), "{document:?}");
3075        }
3076    }
3077
3078    #[test]
3079    fn productive_mutual_object_array_recursion_is_incremental() {
3080        let schema = r##"{
3081            "$defs":{
3082                "object":{"type":"object","properties":{"items":{"$ref":"#/$defs/list"}},"additionalProperties":false},
3083                "list":{"type":"array","items":{"$ref":"#/$defs/object"}}
3084            },
3085            "$ref":"#/$defs/object"
3086        }"##;
3087        let program = program(schema);
3088        assert!(program.uses_incremental_backend());
3089        let mut matcher = program.new_matcher().expect("state");
3090        assert!(matcher
3091            .advance(br#"{"items":[{}, {"items":[]}]}"#)
3092            .expect("document"));
3093        assert!(matcher.is_accepting());
3094    }
3095
3096    #[test]
3097    fn pure_reference_cycles_are_incremental_empty_languages() {
3098        for schema in [
3099            r##"{"$ref":"#"}"##,
3100            r##"{"$defs":{"a":{"$ref":"#/$defs/b"},"b":{"$ref":"#/$defs/a"}},"$ref":"#/$defs/a"}"##,
3101        ] {
3102            let program = StructuredProgram::compile(ir(schema)).expect("positive reference SCC");
3103            assert!(program.uses_incremental_backend());
3104            let mut matcher = program.new_matcher().expect("matcher");
3105            assert!(!matcher.advance(b"null").expect("document"));
3106        }
3107    }
3108
3109    #[test]
3110    fn positive_zero_consumption_cycles_use_bounded_incremental_fixed_points() {
3111        for schema in [
3112            r##"{"$defs":{"x":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3113            r##"{"$defs":{"x":{"$ref":"#/$defs/y"},"y":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3114        ] {
3115            let program = StructuredProgram::compile(ir(schema)).expect("positive union SCC");
3116            let mut matcher = program.new_matcher().expect("matcher");
3117            assert!(matcher.advance(b"null").expect("document"), "{schema}");
3118            assert!(matcher.is_accepting(), "{schema}");
3119        }
3120
3121        let all = StructuredProgram::compile(ir(
3122            r##"{"$defs":{"x":{"allOf":[true,{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3123        ))
3124        .expect("positive intersection SCC");
3125        let mut matcher = all.new_matcher().expect("matcher");
3126        assert!(!matcher.advance(b"null").expect("document"));
3127
3128        let mixed = StructuredProgram::compile(ir(r##"{
3129                "$defs":{
3130                    "x":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/y"}]},
3131                    "y":{"allOf":[true,{"$ref":"#/$defs/x"}]}
3132                },
3133                "$ref":"#/$defs/x"
3134            }"##))
3135        .expect("mixed positive SCC");
3136        let mut matcher = mixed.new_matcher().expect("matcher");
3137        assert!(matcher.advance(b"null").expect("document"));
3138        assert!(matcher.is_accepting());
3139
3140        let error = match StructuredProgram::compile(ir(
3141            r##"{"$defs":{"x":{"oneOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3142        )) {
3143            Ok(_) => panic!("oneOf recursion compiled"),
3144            Err(error) => error,
3145        };
3146        assert_eq!(error.code, ErrorCode::NonMonotoneRecursion);
3147
3148        for schema in [
3149            r##"{"$defs":{"x":{"type":"object","properties":{"next":{"$ref":"#/$defs/x"}},"additionalProperties":false}},"$ref":"#/$defs/x"}"##,
3150            r##"{"$defs":{"x":{"type":"array","items":{"$ref":"#/$defs/x"}}},"$ref":"#/$defs/x"}"##,
3151        ] {
3152            let program = program(schema);
3153            assert!(program.uses_incremental_backend(), "{schema}");
3154            assert_eq!(program.fallback_reason(), None, "{schema}");
3155        }
3156    }
3157
3158    #[test]
3159    fn large_string_enum_selects_incremental_without_a_fallback_reason() {
3160        let program = StructuredProgram::compile(large_string_enum_ir()).expect("program");
3161        assert_eq!(program.backend_kind(), "incremental");
3162        assert_eq!(program.fallback_reason(), None);
3163        assert_eq!(
3164            program.new_matcher().expect("matcher").backend_name(),
3165            "incremental"
3166        );
3167    }
3168
3169    #[test]
3170    fn large_string_enum_accepts_every_member_and_rejects_near_misses() {
3171        let program = StructuredProgram::compile(large_string_enum_ir()).expect("program");
3172        for index in 0..=MAX_UNROLLED_ENUM {
3173            let document = format!(r#""member-{index:04}""#);
3174            let mut matcher = program.new_matcher().expect("matcher");
3175            assert!(matcher.advance(document.as_bytes()).expect("member"));
3176            assert!(matcher.is_accepting(), "{document}");
3177        }
3178        for document in [
3179            r#""member-9999""#,
3180            r#""member-000""#,
3181            r#""member-00000""#,
3182            r#""member-0000-extra""#,
3183        ] {
3184            let mut matcher = program.new_matcher().expect("matcher");
3185            let accepted =
3186                matcher.advance(document.as_bytes()).expect("near miss") && matcher.is_accepting();
3187            assert!(!accepted, "{document}");
3188        }
3189    }
3190
3191    #[test]
3192    fn large_string_enum_token_rejection_is_atomic_and_decoded_spelling_is_canonical() {
3193        let schema = large_string_enum_schema();
3194        for document in [
3195            b"\"a\"".as_slice(),
3196            b"\"\\u0061\"",
3197            "\"é\"".as_bytes(),
3198            b"\"\\u00e9\"",
3199            "\"😀\"".as_bytes(),
3200            b"\"\\uD83D\\uDE00\"",
3201        ] {
3202            let program = program(&schema);
3203            assert!(program.uses_incremental_backend());
3204            let mut matcher = program.new_matcher().expect("matcher");
3205            assert!(matcher.advance(document).expect("document"), "{document:?}");
3206            assert!(matcher.is_accepting(), "{document:?}");
3207        }
3208
3209        let program = program(&schema);
3210        let mut matcher = program.new_matcher().expect("matcher");
3211        assert!(matcher.advance(b"\"ab").expect("prefix"));
3212        let retained = matcher.retained_session_bytes();
3213        assert!(!matcher.advance(b"z-invalid").expect("rejection"));
3214        assert_eq!(matcher.retained_session_bytes(), retained);
3215        assert!(matcher.advance(b"c\"").expect("alternative"));
3216        assert!(matcher.is_accepting());
3217    }
3218
3219    #[test]
3220    fn large_string_enum_compositions_remain_incremental() {
3221        let value = large_string_enum_schema();
3222        let cases = [
3223            (
3224                format!(
3225                    r#"{{"type":"object","properties":{{"v":{value}}},"required":["v"],"additionalProperties":false}}"#
3226                ),
3227                br#"{"v":"a"}"#.as_slice(),
3228            ),
3229            (
3230                format!(
3231                    r#"{{"type":"object","patternProperties":{{"^v$":{value}}},"additionalProperties":false}}"#
3232                ),
3233                br#"{"v":"ab"}"#,
3234            ),
3235            (
3236                format!(r#"{{"type":"object","additionalProperties":{value}}}"#),
3237                br#"{"v":"abc"}"#,
3238            ),
3239            (
3240                format!(r#"{{"type":"array","items":{value}}}"#),
3241                br#"["a","ab"]"#,
3242            ),
3243            (
3244                format!(r#"{{"prefixItems":[{value}],"items":false}}"#),
3245                br#"["abc"]"#,
3246            ),
3247            (
3248                format!(r#"{{"type":"array","contains":{value},"minContains":1}}"#),
3249                br#"[null,"a"]"#,
3250            ),
3251            (
3252                format!(r#"{{"anyOf":[{value},{{"type":"null"}}]}}"#),
3253                b"\"a\"",
3254            ),
3255            (
3256                format!(r#"{{"allOf":[{value},{{"type":"string"}}]}}"#),
3257                b"\"ab\"",
3258            ),
3259            (
3260                format!(r#"{{"oneOf":[{value},{{"const":false}}]}}"#),
3261                b"\"abc\"",
3262            ),
3263            (format!(r#"{{"not":{value}}}"#), b"null"),
3264            (
3265                format!(r##"{{"$defs":{{"v":{value}}},"$ref":"#/$defs/v"}}"##),
3266                b"\"a\"",
3267            ),
3268            (
3269                format!(
3270                    r##"{{"$defs":{{"node":{{"type":"object","properties":{{"v":{value},"next":{{"$ref":"#/$defs/node"}}}},"additionalProperties":false}}}},"$ref":"#/$defs/node"}}"##
3271                ),
3272                br#"{"v":"a","next":{"v":"ab"}}"#,
3273            ),
3274            (
3275                format!(
3276                    r#"{{"type":"object","properties":{{"trigger":true,"v":true}},"dependentSchemas":{{"trigger":{{"properties":{{"v":{value}}},"required":["v"]}}}},"additionalProperties":true}}"#
3277                ),
3278                br#"{"trigger":true,"v":"a"}"#,
3279            ),
3280            (
3281                format!(r#"{{"properties":{{"known":true}},"unevaluatedProperties":{value}}}"#),
3282                br#"{"known":null,"extra":"a"}"#,
3283            ),
3284            (
3285                format!(r#"{{"prefixItems":[true],"unevaluatedItems":{value}}}"#),
3286                br#"[null,"a"]"#,
3287            ),
3288        ];
3289        for (schema, document) in cases {
3290            let program = program(&schema);
3291            assert!(program.uses_incremental_backend(), "{schema}");
3292            let mut matcher = program.new_matcher().expect("matcher");
3293            assert!(matcher.advance(document).expect("document"), "{schema}");
3294            assert!(matcher.is_accepting(), "{schema}");
3295        }
3296    }
3297
3298    #[test]
3299    fn large_string_enum_prefixes_match_the_reference_oracle() {
3300        let schema = large_string_enum_schema();
3301        let schema_ir = ir(&schema);
3302        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3303        assert!(program.uses_incremental_backend());
3304        for document in [b"\"abc\"".as_slice(), b"\"abd\"", b"\"\\u0061\""] {
3305            for end in 0..=document.len() {
3306                let prefix = &document[..end];
3307                for next in [
3308                    0u8, b'"', b'\\', b'a', b'b', b'c', b'z', 0x80, 0xc3, 0xe9, 0xff,
3309                ] {
3310                    let mut incremental = program.new_matcher().expect("matcher");
3311                    let mut reference = ReferenceMatcher::new(schema_ir.clone());
3312                    let incremental_prefix =
3313                        prefix.is_empty() || incremental.advance(prefix).expect("prefix");
3314                    let reference_prefix = prefix.is_empty() || reference.advance(prefix);
3315                    assert_eq!(incremental_prefix, reference_prefix, "{prefix:?}");
3316                    if incremental_prefix {
3317                        assert_eq!(
3318                            incremental.advance(&[next]).expect("next"),
3319                            reference.advance(&[next]),
3320                            "prefix={prefix:?}, next={next:#04x}"
3321                        );
3322                        assert_eq!(incremental.is_accepting(), reference.is_accepting());
3323                        assert_eq!(incremental.is_dead(), reference.is_dead());
3324                    }
3325                }
3326            }
3327        }
3328    }
3329
3330    #[test]
3331    fn recursive_ref_every_prefix_matches_reference_oracle() {
3332        let schema = r##"{
3333            "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3334            "$ref":"#/$defs/node"
3335        }"##;
3336        let schema_ir = ir(schema);
3337        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3338        let documents: &[&[u8]] = &[b"{}", br#"{"next":{}}"#, br#"{"next":{"next":{}}}"#];
3339        let deviations: &[&[u8]] = &[
3340            b"\0",
3341            b"{",
3342            b"}",
3343            b"[",
3344            b"]",
3345            b":",
3346            b",",
3347            b"\"",
3348            b"\\",
3349            b" ",
3350            b"\n",
3351            b"\xc2",
3352            b"\x80",
3353            b"\xff",
3354            br#"{"next":"#,
3355        ];
3356
3357        for document in documents {
3358            let mut matcher = program.new_matcher().expect("matcher");
3359            for end in 0..=document.len() {
3360                let prefix = &document[..end];
3361                assert_eq!(
3362                    matcher.is_accepting(),
3363                    super::super::reference::accepts(&schema_ir, prefix),
3364                    "acceptance at {prefix:?}"
3365                );
3366                let mut records: Vec<(Vec<u8>, Vec<u32>)> = deviations
3367                    .iter()
3368                    .enumerate()
3369                    .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
3370                    .collect();
3371                if let Some(&next) = document.get(end) {
3372                    records.push((vec![next], vec![30]));
3373                    records.push((document[end..].to_vec(), vec![31]));
3374                }
3375                records.push((b"}".to_vec(), vec![32, 33]));
3376                let expected = matcher
3377                    .allowed_mask_from_records(
3378                        40,
3379                        records
3380                            .iter()
3381                            .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
3382                    )
3383                    .expect("mask");
3384                for _ in 0..100 {
3385                    let repeated = matcher
3386                        .allowed_mask_from_records(
3387                            40,
3388                            records
3389                                .iter()
3390                                .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
3391                        )
3392                        .expect("repeated mask");
3393                    assert_eq!(repeated, expected, "mask mutated state at {prefix:?}");
3394                }
3395                for (bytes, ids) in &records {
3396                    let mut candidate = prefix.to_vec();
3397                    candidate.extend_from_slice(bytes);
3398                    let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3399                        || super::super::reference::can_continue(&schema_ir, &candidate);
3400                    for &id in ids {
3401                        assert_eq!(
3402                            expected.get(TokenId(id)),
3403                            oracle,
3404                            "prefix {prefix:?}, candidate {bytes:?}"
3405                        );
3406                    }
3407                }
3408                if let Some(&next) = document.get(end) {
3409                    assert!(matcher.advance(&[next]).expect("valid next byte"));
3410                }
3411            }
3412        }
3413    }
3414
3415    #[test]
3416    fn recursive_ref_trie_record_and_reference_masks_are_identical() {
3417        let schema = r##"{
3418            "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3419            "$ref":"#/$defs/node"
3420        }"##;
3421        let schema_ir = ir(schema);
3422        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3423        let mut tokens = FxHashMap::default();
3424        let pieces: &[&[u8]] = &[
3425            b"{",
3426            b"}",
3427            b":",
3428            b",",
3429            b"\"next\"",
3430            br#"{"next":"#,
3431            b"{}",
3432            b"}}",
3433            b" ",
3434            b" \n",
3435            b"null",
3436            b"[",
3437            b"\xff",
3438        ];
3439        for (id, piece) in pieces.iter().enumerate() {
3440            tokens.insert(piece.to_vec(), vec![u32::try_from(id).unwrap()]);
3441        }
3442        tokens.insert(b"}".to_vec(), vec![1, 20]);
3443        let width = 32usize;
3444        let handle = VocabularyHandle::new(Arc::new(build_vocabulary(31, tokens).expect("vocab")))
3445            .expect("handle");
3446        let trie = TrieCache::new().bind(&handle).expect("trie");
3447        let mut walked = program.new_matcher().expect("trie matcher");
3448        let mut scanned = program.new_matcher().expect("record matcher");
3449        let mut prefix = Vec::new();
3450        for step in [b"{".as_slice(), b"\"next\"", b":", b"{", b"}", b"}"] {
3451            let record = scanned
3452                .allowed_mask_from_records(width, handle.iter_records())
3453                .expect("record mask");
3454            let mut output = vec![0xff; width.div_ceil(32) * 4];
3455            for _ in 0..100 {
3456                walked
3457                    .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
3458                    .expect("trie mask");
3459                for id in 0..width {
3460                    let token = TokenId(u32::try_from(id).unwrap());
3461                    assert_eq!(
3462                        output[id / 8] & (1u8 << (id % 8)) != 0,
3463                        record.get(token),
3464                        "trie vs record at {prefix:?}, id {id}"
3465                    );
3466                }
3467            }
3468            for (bytes, ids) in handle.iter_records() {
3469                let mut candidate = prefix.clone();
3470                candidate.extend_from_slice(bytes);
3471                let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3472                    || super::super::reference::can_continue(&schema_ir, &candidate);
3473                for &id in ids {
3474                    assert_eq!(record.get(TokenId(id)), oracle, "reference at id {id}");
3475                }
3476            }
3477            assert!(walked.advance(step).expect("trie step"));
3478            assert!(scanned.advance(step).expect("record step"));
3479            prefix.extend_from_slice(step);
3480        }
3481        assert!(walked.is_accepting() && scanned.is_accepting());
3482    }
3483
3484    #[test]
3485    fn dependent_schemas_use_incremental_backend_and_validate_the_whole_object() {
3486        let program = program(
3487            r#"{"type":"object","dependentSchemas":{"x":{"type":"object","required":["y"],"additionalProperties":true}},"additionalProperties":true}"#,
3488        );
3489        assert_eq!(program.backend_kind(), "incremental");
3490        for (document, expected) in [
3491            (br#"{}"#.as_slice(), true),
3492            (br#"{"y":1}"#.as_slice(), true),
3493            (br#"{"x":1}"#.as_slice(), false),
3494            (br#"{"x":1,"y":2}"#.as_slice(), true),
3495            (br#"{"y":2,"x":1}"#.as_slice(), true),
3496            (br#"{"\u0078":1,"y":2}"#.as_slice(), true),
3497        ] {
3498            let mut matcher = program.new_matcher().expect("state");
3499            let actual = matcher.advance(document).expect("advance") && matcher.is_accepting();
3500            assert_eq!(actual, expected, "{}", String::from_utf8_lossy(document));
3501        }
3502    }
3503
3504    #[test]
3505    fn dependent_schemas_hard_shapes_and_nested_locations_match_reference() {
3506        let cases: &[(&str, &[&[u8]])] = &[
3507            (
3508                r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]},"c":{"properties":{"n":{"type":"integer"}}}},"additionalProperties":true}"#,
3509                &[
3510                    br#"{}"#,
3511                    br#"{"b":1}"#,
3512                    br#"{"a":1}"#,
3513                    br#"{"b":1,"a":2}"#,
3514                    br#"{"a":2,"b":1}"#,
3515                    br#"{"c":1,"n":"x"}"#,
3516                ],
3517            ),
3518            (
3519                r#"{"type":"object","dependentSchemas":{"a":{"patternProperties":{"^x":{"type":"integer"}},"propertyNames":{"pattern":"^[ax]+$"},"minProperties":2,"maxProperties":3}},"additionalProperties":true}"#,
3520                &[
3521                    br#"{"a":1,"x":2}"#,
3522                    br#"{"x":2,"a":1}"#,
3523                    br#"{"a":1,"x":"bad"}"#,
3524                    br#"{"a":1,"other":2}"#,
3525                ],
3526            ),
3527            (
3528                r#"{"type":"object","dependentSchemas":{"a":{"properties":{"items":{"type":"array","items":{"type":"integer"}}}}},"additionalProperties":true}"#,
3529                &[
3530                    br#"{"items":[1,2],"a":true}"#,
3531                    br#"{"a":true,"items":[1,"x"]}"#,
3532                ],
3533            ),
3534            (
3535                r#"{"type":"object","dependentSchemas":{"a":{"allOf":[{"required":["x"]},{"anyOf":[{"required":["y"]},{"not":{"required":["z"]}}]}]}},"additionalProperties":true}"#,
3536                &[
3537                    br#"{"a":1,"x":1,"y":1}"#,
3538                    br#"{"x":1,"a":1}"#,
3539                    br#"{"a":1,"y":1}"#,
3540                ],
3541            ),
3542            (
3543                r#"{"type":"object","properties":{"child":{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}},"dependentSchemas":{"p":{"required":["child"]}},"additionalProperties":true}"#,
3544                &[
3545                    br#"{"p":1,"child":{"b":1,"a":1}}"#,
3546                    br#"{"child":{"a":1},"p":1}"#,
3547                ],
3548            ),
3549            (
3550                r#"{"type":"array","items":{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}}"#,
3551                &[br#"[{"b":1,"a":1},{}]"#, br#"[{"a":1}]"#],
3552            ),
3553            (
3554                r#"{"anyOf":[{"type":"object","dependentSchemas":{"a":false}},{"type":"array"}]}"#,
3555                &[br#"{}"#, br#"{"a":1}"#, br#"[]"#],
3556            ),
3557            (
3558                r#"{"not":{"type":"object","dependentSchemas":{"a":false},"required":["a"]}}"#,
3559                &[br#"{}"#, br#"{"a":1}"#, b"null"],
3560            ),
3561            (
3562                r#"{"dependentSchemas":{"a":false}}"#,
3563                &[b"null", b"true", br#""text""#, b"12", b"[]", br#"{"a":1}"#],
3564            ),
3565        ];
3566        for (schema, documents) in cases {
3567            let schema_ir = ir(schema);
3568            let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3569            assert_eq!(program.backend_kind(), "incremental", "{schema}");
3570            for &document in *documents {
3571                let expected = super::super::reference::accepts(&schema_ir, document);
3572                let mut matcher = program.new_matcher().expect("matcher");
3573                let actual = matcher.advance(document).expect("advance") && matcher.is_accepting();
3574                assert_eq!(actual, expected, "{schema}: {document:?}");
3575            }
3576        }
3577    }
3578
3579    #[test]
3580    fn dependent_schemas_prefixes_match_the_reference() {
3581        type Case<'a> = (&'a str, &'a [(&'a [u8], bool)]);
3582        let cases: &[Case<'_>] = &[
3583            (
3584                r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}"#,
3585                &[
3586                    (br#"{}"#, true),
3587                    (br#"{"b":1}"#, true),
3588                    (br#"{"a":1}"#, false),
3589                    (br#"{"a":1,"b":2}"#, true),
3590                    (br#"{"b":2,"a":1}"#, true),
3591                ],
3592            ),
3593            (
3594                r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}"#,
3595                &[(br#"{"a":1,"b":2}"#, true), (br#"{"a":1}"#, false)],
3596            ),
3597            (
3598                r#"{"type":"object","dependentSchemas":{"a":{"required":["y"]},"b":{"required":["z"]}},"additionalProperties":true}"#,
3599                &[
3600                    (br#"{"a":1,"b":2,"y":1,"z":2}"#, true),
3601                    (br#"{"a":1,"b":2,"y":1}"#, false),
3602                    (br#"{"a":1,"y":1}"#, true),
3603                ],
3604            ),
3605            (
3606                r#"{"type":"object","dependentSchemas":{"a":{"not":{"required":["z"]}}},"additionalProperties":true}"#,
3607                &[(br#"{"a":1}"#, true), (br#"{"a":1,"z":1}"#, false)],
3608            ),
3609        ];
3610        let mut document_total = 0usize;
3611        let mut prefix_total = 0usize;
3612        for (schema, documents) in cases {
3613            for &(document, valid) in *documents {
3614                document_total += 1;
3615                prefix_total += assert_combinator_prefixes(schema, document, valid);
3616            }
3617        }
3618        println!("dependentSchemas documents={document_total} byte_prefixes={prefix_total}");
3619    }
3620
3621    /// Closing key `a` triggers a false dependent schema, so every completion fails.
3622    /// The reference continuation check evaluates this dependency only when the object closes.
3623    #[test]
3624    fn a_false_triggered_dependency_dies_before_reference_can_continue_notices() {
3625        let schema =
3626            r#"{"type":"object","dependentSchemas":{"a":false},"additionalProperties":true}"#;
3627        let schema_ir = ir(schema);
3628        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3629        assert!(program.uses_incremental_backend());
3630        let mut matcher = program.new_matcher().expect("matcher");
3631        assert!(matcher.advance(br#"{"a"#).expect("key body still open"));
3632        let before = matcher.is_accepting();
3633        assert!(
3634            !matcher
3635                .advance(b"\"")
3636                .expect("closing quote fixes the doomed trigger"),
3637            "the closing quote that commits key `a` must be rejected outright"
3638        );
3639        assert_eq!(
3640            matcher.is_accepting(),
3641            before,
3642            "rejection must not mutate state"
3643        );
3644
3645        let vocab_tail: &[&[u8]] = &[b":1}", b":1,\"z\":2}", b":null}", b":{}}", b":[1,2]}"];
3646        for &tail in vocab_tail {
3647            let mut completed = b"{\"a\"".to_vec();
3648            completed.extend_from_slice(tail);
3649            assert!(
3650                !super::super::reference::accepts(&schema_ir, &completed),
3651                "{completed:?} should never validate once trigger `a` is fixed"
3652            );
3653        }
3654
3655        let mut record = program.new_matcher().expect("record matcher");
3656        assert!(record.advance(br#"{"a"#).expect("key body still open"));
3657        // The mask at this exact position must exclude the closing quote (it would fix the doomed
3658        // trigger) while a token that instead extends the key spelling remains a live continuation.
3659        let candidates: &[&[u8]] = &[b"\"", b"b", b"a"];
3660        let mut tokens = FxHashMap::default();
3661        for (id, bytes) in candidates.iter().enumerate() {
3662            tokens.insert(bytes.to_vec(), vec![u32::try_from(id).unwrap()]);
3663        }
3664        let handle = VocabularyHandle::new(Arc::new(
3665            build_vocabulary(u32::try_from(candidates.len()).unwrap(), tokens).unwrap(),
3666        ))
3667        .unwrap();
3668        let mask = record
3669            .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
3670            .unwrap();
3671        for (id, &candidate) in candidates.iter().enumerate() {
3672            let allowed = mask.get(TokenId(u32::try_from(id).unwrap()));
3673            assert_eq!(
3674                allowed,
3675                candidate != b"\"",
3676                "candidate {candidate:?} mask bit"
3677            );
3678        }
3679    }
3680
3681    #[test]
3682    fn dependent_schema_cycles_are_typed_errors_and_annotation_consumers_are_incremental() {
3683        for schema in [
3684            r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"anyOf":[true,{"$ref":"#/$defs/x"}]}}}},"$ref":"#/$defs/x"}"##,
3685            r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"not":{"$ref":"#/$defs/x"}}}}},"$ref":"#/$defs/x"}"##,
3686            r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"$ref":"#/$defs/y"}}},"y":{"$ref":"#/$defs/x"}},"$ref":"#/$defs/x"}"##,
3687        ] {
3688            let error = match StructuredProgram::compile(ir(schema)) {
3689                Ok(_) => panic!("dependent recursion compiled: {schema}"),
3690                Err(error) => error,
3691            };
3692            assert_eq!(error.code, ErrorCode::NonMonotoneRecursion, "{schema}");
3693            assert!(!error.recoverable, "{schema}");
3694        }
3695        for schema in [
3696            r#"{"type":"object","dependentSchemas":{"a":{"properties":{"x":true}}},"unevaluatedProperties":false}"#,
3697            r#"{"dependentSchemas":{"a":{"items":true}},"unevaluatedItems":false}"#,
3698        ] {
3699            let compiled = StructuredProgram::compile(ir(schema))
3700                .unwrap_or_else(|error| panic!("{schema}: {error:?}"));
3701            assert_eq!(compiled.backend_kind(), "incremental", "{schema}");
3702        }
3703        let dependent_required = ir(r#"{"type":"object","dependentRequired":{"a":["b"]}}"#);
3704        assert_eq!(dependent_required.diagnostics().len(), 0);
3705        let program = StructuredProgram::compile(dependent_required).unwrap();
3706        assert!(program.uses_incremental_backend());
3707    }
3708
3709    #[test]
3710    fn dependent_required_is_non_annotating_under_unevaluated_properties() {
3711        let only_presence =
3712            ir(r#"{"dependentRequired":{"a":["b"]},"unevaluatedProperties":false}"#);
3713        let program = StructuredProgram::compile(only_presence).unwrap();
3714        assert_eq!(program.backend_kind(), "incremental");
3715        let mut matcher = program.new_matcher().unwrap();
3716        assert!(!matcher.advance(br#"{"a":1,"b":2}"#).unwrap() || !matcher.is_accepting());
3717
3718        let annotated = ir(r#"{
3719            "properties":{"a":true,"b":true},
3720            "dependentRequired":{"a":["b"]},
3721            "unevaluatedProperties":false
3722        }"#);
3723        let program = StructuredProgram::compile(annotated).unwrap();
3724        assert_eq!(program.backend_kind(), "incremental");
3725        let mut matcher = program.new_matcher().unwrap();
3726        assert!(matcher.advance(br#"{"a":1,"b":2}"#).unwrap());
3727        assert!(matcher.is_accepting());
3728    }
3729
3730    #[test]
3731    fn dependent_required_combinations_remain_incremental_and_correct() {
3732        let cases = [
3733            (
3734                r#"{"type":"object","properties":{"a":true,"b":true},"dependentRequired":{"a":["b"]},"additionalProperties":false}"#,
3735                br#"{"a":1,"b":2}"#.as_slice(),
3736                true,
3737            ),
3738            (
3739                r#"{"type":"object","required":["a"],"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3740                br#"{"a":1}"#,
3741                false,
3742            ),
3743            (
3744                r#"{"type":"object","patternProperties":{"^b$":true},"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3745                br#"{"b":2,"a":1}"#,
3746                true,
3747            ),
3748            (
3749                r#"{"type":"object","propertyNames":{"minLength":1},"minProperties":1,"maxProperties":2,"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3750                br#"{"a":1,"b":2}"#,
3751                true,
3752            ),
3753            (
3754                r#"{"type":"object","dependentSchemas":{"a":{"required":["c"]}},"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3755                br#"{"a":1,"b":2,"c":3}"#,
3756                true,
3757            ),
3758            (
3759                r#"{"allOf":[{"dependentRequired":{"a":["b"]}},{"type":"object"}]}"#,
3760                br#"{"a":1}"#,
3761                false,
3762            ),
3763            (
3764                r#"{"anyOf":[{"dependentRequired":{"a":["b"]}},{"type":"array"}]}"#,
3765                br#"[]"#,
3766                true,
3767            ),
3768            (
3769                r#"{"oneOf":[{"dependentRequired":{"a":["b"]}},{"type":"array"}]}"#,
3770                br#"{"a":1,"b":2}"#,
3771                true,
3772            ),
3773            (
3774                r#"{"not":{"dependentRequired":{"a":["b"]}}}"#,
3775                br#"{"a":1}"#,
3776                true,
3777            ),
3778            (
3779                r#"{"if":{"required":["a"]},"then":{"dependentRequired":{"a":["b"]}},"else":true}"#,
3780                br#"{"a":1,"b":2}"#,
3781                true,
3782            ),
3783        ];
3784        for (schema, document, expected) in cases {
3785            let schema_ir = ir(schema);
3786            let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3787            assert!(program.uses_incremental_backend(), "{schema}");
3788            let mut matcher = program.new_matcher().unwrap();
3789            let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3790            assert_eq!(actual, expected, "{schema} / {document:?}");
3791            assert_eq!(
3792                actual,
3793                super::super::reference::accepts(&schema_ir, document)
3794            );
3795        }
3796    }
3797
3798    #[test]
3799    fn dependent_required_hard_presence_graphs_nested_and_recursive_cases() {
3800        let cases = [
3801            (
3802                r#"{"dependentRequired":{"a":["shared"],"x":["shared"]}}"#,
3803                br#"{"a":1,"x":2,"shared":3}"#.as_slice(),
3804                true,
3805            ),
3806            (
3807                r#"{"dependentRequired":{"a":["b"],"b":["a"]}}"#,
3808                br#"{"a":1}"#,
3809                false,
3810            ),
3811            (
3812                r#"{"dependentRequired":{"a":["b"],"b":["c"]}}"#,
3813                br#"{"a":1,"b":2}"#,
3814                false,
3815            ),
3816            (
3817                r#"{"dependentRequired":{"a":["b"],"b":["c"]}}"#,
3818                br#"{"c":3,"b":2,"a":1}"#,
3819                true,
3820            ),
3821            (
3822                r#"{"type":"object","properties":{"nested":{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}},"additionalProperties":true}"#,
3823                br#"{"nested":{"a":1}}"#,
3824                false,
3825            ),
3826            (
3827                r#"{"type":"array","items":{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}}"#,
3828                br#"[{"a":1,"b":2},{"x":3}]"#,
3829                true,
3830            ),
3831            (
3832                r##"{"$defs":{"node":{"type":"object","dependentRequired":{"a":["b"]},"properties":{"next":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/node"}]}},"additionalProperties":true}},"$ref":"#/$defs/node"}"##,
3833                br#"{"a":1,"b":2,"next":{"a":3,"b":4,"next":null}}"#,
3834                true,
3835            ),
3836        ];
3837        for (schema, document, expected) in cases {
3838            let schema_ir = ir(schema);
3839            let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3840            assert!(program.uses_incremental_backend(), "{schema}");
3841            let mut matcher = program.new_matcher().unwrap();
3842            let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3843            assert_eq!(actual, expected, "{schema} / {document:?}");
3844            assert_eq!(
3845                actual,
3846                super::super::reference::accepts(&schema_ir, document)
3847            );
3848        }
3849    }
3850
3851    #[test]
3852    fn dependent_required_remaining_keyword_routing_is_explicit() {
3853        for (schema, document, expected) in [
3854            (
3855                r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":{"type":"integer"}}"#,
3856                br#"{"a":1,"b":2}"#.as_slice(),
3857                true,
3858            ),
3859            (
3860                r#"{"dependentRequired":{"a":["b"]},"unevaluatedItems":false}"#,
3861                br#"{"a":1,"b":2}"#,
3862                true,
3863            ),
3864        ] {
3865            let schema_ir = ir(schema);
3866            let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3867            assert!(program.uses_incremental_backend());
3868            let mut matcher = program.new_matcher().unwrap();
3869            let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3870            assert_eq!(actual, expected);
3871            assert_eq!(
3872                actual,
3873                super::super::reference::accepts(&schema_ir, document)
3874            );
3875        }
3876    }
3877
3878    #[test]
3879    fn dependent_object_presence_record_preallocated_trie_and_reference_masks_are_identical() {
3880        for schema in [
3881            r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]},"never":false},"additionalProperties":true}"#,
3882            r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3883        ] {
3884            let mut mask_comparisons = 0usize;
3885            let schema_ir = ir(schema);
3886            let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3887            let mut tokens = FxHashMap::default();
3888            for (id, bytes) in [
3889                (0, b"{".as_slice()),
3890                (1, b"\"a\""),
3891                (2, b"\"\\u0061\""),
3892                (3, b":"),
3893                (4, b"1"),
3894                (5, b","),
3895                (6, b"\"b\":"),
3896                (7, b"2}"),
3897                (8, b"}"),
3898                (9, b" \t"),
3899                (10, b"\"other\":1"),
3900                (11, b"1,"),
3901                (12, b"}\n,"),
3902                (13, b"\"a"),
3903                (14, b"\\u00"),
3904                (15, b"61\""),
3905            ] {
3906                tokens.insert(bytes.to_vec(), vec![id]);
3907            }
3908            let handle =
3909                VocabularyHandle::new(Arc::new(build_vocabulary(16, tokens).unwrap())).unwrap();
3910            let trie = TrieCache::new().bind(&handle).unwrap();
3911            let mut record = program.new_matcher().unwrap();
3912            let mut walked = program.new_matcher().unwrap();
3913            let mut prefix = Vec::new();
3914            for token in [
3915                b"{".as_slice(),
3916                b"\"b\":",
3917                b"2",
3918                b",",
3919                b"\"\\u0061\"",
3920                b":",
3921                b"1",
3922                b"}",
3923            ] {
3924                let allocating = record
3925                    .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
3926                    .unwrap();
3927                let mut preallocated = [0xff; 4];
3928                record
3929                    .write_record_mask_le_bytes_into(
3930                        handle.mask_vocab_size(),
3931                        handle.iter_records(),
3932                        &mut preallocated,
3933                    )
3934                    .unwrap();
3935                let mut trie_output = [0xff; 4];
3936                walked
3937                    .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_output)
3938                    .unwrap();
3939                for (bytes, ids) in handle.iter_records() {
3940                    let mut candidate = prefix.clone();
3941                    candidate.extend_from_slice(bytes);
3942                    let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3943                        || super::super::reference::can_continue(&schema_ir, &candidate);
3944                    for &id in ids {
3945                        let id = usize::try_from(id).unwrap();
3946                        assert_eq!(
3947                            allocating.get(TokenId(id as u32)),
3948                            oracle,
3949                            "{prefix:?} + {bytes:?}"
3950                        );
3951                        assert_eq!(preallocated[id / 8] & (1 << (id % 8)) != 0, oracle);
3952                        assert_eq!(trie_output[id / 8] & (1 << (id % 8)) != 0, oracle);
3953                        mask_comparisons += 3;
3954                    }
3955                }
3956                assert!(record.advance(token).unwrap(), "{token:?}");
3957                assert!(walked.advance(token).unwrap(), "{token:?}");
3958                prefix.extend_from_slice(token);
3959            }
3960            assert!(record.is_accepting() && walked.is_accepting());
3961            println!("dependent-object token-mask comparisons={mask_comparisons} schema={schema}");
3962        }
3963    }
3964
3965    #[test]
3966    fn rejected_candidate_rolls_back_incremental_state() {
3967        let program = program(
3968            r#"{"type":"object","required":["x"],"properties":{"x":{"type":"integer"}},"additionalProperties":false}"#,
3969        );
3970        let mut matcher = program.new_matcher().expect("state");
3971        assert!(matcher.advance(br#"{"#).expect("advance"));
3972        let before = matcher.is_accepting();
3973        assert!(!matcher.advance(b"}").expect("advance"));
3974        assert_eq!(matcher.is_accepting(), before);
3975        assert!(matcher.advance(b"\"x\"").expect("advance"));
3976    }
3977
3978    #[test]
3979    fn mask_evaluation_rolls_back_and_preserves_duplicate_ids() {
3980        let program = program(
3981            r#"{"type":"object","properties":{"x":{"type":"boolean"}},"required":["x"],"additionalProperties":false}"#,
3982        );
3983        let mut matcher = program.new_matcher().expect("state");
3984        assert!(matcher.advance(br#"{"x":"#).expect("advance"));
3985        let records = [
3986            (b"true".as_slice(), [3u32, 7].as_slice()),
3987            (b"0".as_slice(), [9u32].as_slice()),
3988        ];
3989        let mask = matcher
3990            .allowed_mask_from_records(10, records.into_iter())
3991            .expect("mask");
3992        assert!(mask.get(TokenId(3)) && mask.get(TokenId(7)));
3993        assert!(!mask.get(TokenId(9)));
3994        assert!(matcher.advance(b"true").expect("advance"));
3995    }
3996
3997    #[test]
3998    fn committed_object_keys_remain_unique() {
3999        let schema = r#"{"type":"object","properties":{"name":{"type":"boolean"}},"required":["name"],"additionalProperties":{"type":"boolean"}}"#;
4000        let ir = Arc::new(schema_to_ir(schema, CompileOptions::default()).expect("schema"));
4001        let program = StructuredProgram::compile(ir).expect("program");
4002        let mut matcher = program.new_matcher().expect("state");
4003        for token in [b"{".as_slice(), b"\"name\"", b":", b"true", b","] {
4004            assert!(matcher.advance(token).expect("advance"));
4005        }
4006        assert!(!matcher.advance(b"\"name\"").expect("duplicate"));
4007    }
4008
4009    #[test]
4010    fn supported_structured_shapes_select_incremental_backend() {
4011        let schemas = [
4012            r#"{"type":"object","properties":{"x":{"type":"integer"}},"additionalProperties":{"type":"string"}}"#,
4013            r#"{"type":"object","patternProperties":{"^x+$":{"type":"integer"}},"additionalProperties":false}"#,
4014            r#"{"type":"object","propertyNames":{"type":"string","pattern":"^a+$"},"minProperties":1,"maxProperties":2,"additionalProperties":true}"#,
4015            r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"integer","minimum":1},"minContains":1,"maxContains":2,"uniqueItems":true}"#,
4016            r#"{"type":"object","properties":{"\u0061":{"type":"array","items":{"type":"object","properties":{"n":{"type":"number"}},"required":["n"],"additionalProperties":false},"uniqueItems":true}},"additionalProperties":false}"#,
4017        ];
4018        for schema in schemas {
4019            assert_eq!(program(schema).backend_kind(), "incremental", "{schema}");
4020        }
4021    }
4022
4023    #[test]
4024    fn plan_limit_is_an_error_not_a_reference_fallback() {
4025        let limits = StructuredLimits {
4026            max_plan_bytes: 0,
4027            ..StructuredLimits::default()
4028        };
4029        let result = StructuredProgram::compile_with_limits(
4030            ir(r#"{"type":"string","pattern":"^a+$"}"#),
4031            limits,
4032        );
4033        let error = match result {
4034            Ok(_) => panic!("zero plan budget compiled"),
4035            Err(error) => error,
4036        };
4037        assert_eq!(error.code, ErrorCode::InternalLimitExceeded);
4038        assert!(!error.recoverable);
4039    }
4040
4041    #[test]
4042    fn incremental_program_has_no_fallback_reason() {
4043        let program = program(r#"{"type":"string"}"#);
4044        assert!(program.uses_incremental_backend());
4045        assert_eq!(program.fallback_reason(), None);
4046    }
4047
4048    #[test]
4049    fn every_supported_node_shape_has_an_explicit_runtime_route() {
4050        let large_enum = large_string_enum_schema();
4051        let schemas = [
4052            r#"{"type":"null"}"#.to_owned(),
4053            r#"{"type":"boolean"}"#.to_owned(),
4054            "false".to_owned(),
4055            r#"{"const":"x"}"#.to_owned(),
4056            r#"{"type":"string","pattern":"^x+$"}"#.to_owned(),
4057            r#"{"type":"integer"}"#.to_owned(),
4058            r#"{"type":"number"}"#.to_owned(),
4059            r#"{"enum":["a","b"]}"#.to_owned(),
4060            large_enum,
4061            r#"{"type":"array","items":{"type":"integer"},"maxItems":100}"#.to_owned(),
4062            r#"{"prefixItems":[{"type":"integer"}],"items":false}"#.to_owned(),
4063            r#"{"prefixItems":[{"type":"integer"}],"items":true}"#.to_owned(),
4064            r#"{"type":"array","items":{"type":"object"},"uniqueItems":true,"contains":{"properties":{"k":{"const":1}}}}"#.to_owned(),
4065            r#"{"type":"object","properties":{"a":{"type":"integer"}},"additionalProperties":false}"#.to_owned(),
4066            r#"{"type":"object","patternProperties":{"^x$":{"type":"integer"}},"additionalProperties":true}"#.to_owned(),
4067            r#"{"anyOf":[{"type":"null"},{"type":"boolean"}]}"#.to_owned(),
4068            r#"{"allOf":[{"type":"integer"},{"minimum":0}]}"#.to_owned(),
4069            r#"{"oneOf":[{"type":"null"},{"type":"boolean"}]}"#.to_owned(),
4070            r#"{"not":{"type":"null"}}"#.to_owned(),
4071            r##"{"$defs":{"x":{"type":"string"}},"$ref":"#/$defs/x"}"##.to_owned(),
4072            r#"{"properties":{"a":true},"unevaluatedProperties":false}"#.to_owned(),
4073        ];
4074        for schema in schemas {
4075            let program = program(&schema);
4076            assert_ne!(
4077                program.fallback_reason(),
4078                Some("unsupported-node-shape"),
4079                "{schema}"
4080            );
4081            assert!(program.uses_incremental_backend(), "{schema}");
4082        }
4083    }
4084
4085    #[test]
4086    fn former_fallbacks_are_incremental_or_typed_errors() {
4087        let mut builder = crate::ir::Builder::new(CompileOptions::default());
4088        let lower = builder
4089            .string_pattern("a+", Some(5), None, Charset::Utf8Any)
4090            .expect("lower");
4091        let upper = builder
4092            .string_pattern("a+", None, Some(2), Charset::Utf8Any)
4093            .expect("upper");
4094        let root = builder
4095            .intersection_of(vec![lower, upper])
4096            .expect("intersection");
4097        let intersection = StructuredProgram::compile(Arc::new(builder.finish(root).expect("ir")))
4098            .expect("program");
4099        assert!(intersection.uses_incremental_backend());
4100        let mut matcher = intersection.new_matcher().expect("matcher");
4101        assert!(!matcher.advance(br#""aaaaa""#).expect("document"));
4102
4103        let mut builder = crate::ir::Builder::new(CompileOptions::default());
4104        let value = builder.string_const("x").expect("value");
4105        let fields = (0..5).map(|index| (format!("p{index}"), value)).collect();
4106        let root = builder
4107            .object(
4108                fields,
4109                &[false; 5],
4110                crate::ir::ObjectClosure::RejectOpenObjects,
4111            )
4112            .expect("object");
4113        let error = match StructuredProgram::compile(Arc::new(builder.finish(root).expect("ir"))) {
4114            Ok(_) => panic!("RejectOpenObjects compiled"),
4115            Err(error) => error,
4116        };
4117        assert_eq!(error.code, ErrorCode::Unsupported);
4118        assert!(!error.recoverable);
4119    }
4120
4121    #[test]
4122    fn resource_errors_roll_back_token_and_mask_state() {
4123        let limits = StructuredLimits {
4124            max_document_bytes: 1,
4125            max_mask_work: 1,
4126            ..StructuredLimits::default()
4127        };
4128        let program = StructuredProgram::compile_with_limits(ir(r#"{"type":"string"}"#), limits)
4129            .expect("program");
4130        let mut matcher = program.new_matcher().expect("matcher");
4131        let error = matcher.advance(b"\"a").expect_err("document cap");
4132        assert!(matches!(
4133            error,
4134            StructuredMatcherError::ResourceLimit {
4135                kind: LimitKind::DocumentBytes,
4136                observed: 2,
4137                limit: 1,
4138            }
4139        ));
4140        assert!(!matcher.is_dead());
4141
4142        let records = [(b"\"".to_vec(), vec![1u32, 2])];
4143        let error = matcher
4144            .allowed_mask_from_records(
4145                3,
4146                records
4147                    .iter()
4148                    .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
4149            )
4150            .expect_err("mask work cap");
4151        assert!(matches!(
4152            error,
4153            StructuredMatcherError::ResourceLimit {
4154                kind: LimitKind::MaskWork,
4155                ..
4156            }
4157        ));
4158        assert!(matcher.advance(b"\"").expect("rollback kept empty state"));
4159    }
4160
4161    #[test]
4162    fn masks_are_order_independent_and_match_the_reference_oracle() {
4163        let schema = r#"{"type":"object","properties":{"n":{"type":"number","minimum":-1,"maximum":1,"multipleOf":0.5},"s":{"type":"string","pattern":"^a+$","minLength":1,"maxLength":2}},"required":["n","s"],"additionalProperties":false}"#;
4164        let ir = ir(schema);
4165        let program = StructuredProgram::compile(ir.clone()).expect("program");
4166        assert_eq!(program.backend_kind(), "incremental");
4167        let prefix = br#"{"n":1,"s":"a"#;
4168        let records = vec![
4169            (b"\"}".to_vec(), vec![0, 6]),
4170            (b"a\"}".to_vec(), vec![1]),
4171            (b"aa\"}".to_vec(), vec![2]),
4172            (b"b\"}".to_vec(), vec![3]),
4173        ];
4174        let mut forward = program.new_matcher().expect("matcher");
4175        let mut reverse = program.new_matcher().expect("matcher");
4176        for &byte in prefix {
4177            assert!(forward.advance(&[byte]).expect("prefix"));
4178            assert!(reverse.advance(&[byte]).expect("prefix"));
4179        }
4180        let forward_mask = mask(&mut forward, 7, &records);
4181        let mut reversed = records.clone();
4182        reversed.reverse();
4183        let reverse_mask = mask(&mut reverse, 7, &reversed);
4184        assert_eq!(forward_mask, reverse_mask);
4185
4186        for (bytes, ids) in &records {
4187            let mut candidate = prefix.to_vec();
4188            candidate.extend_from_slice(bytes);
4189            let expected = super::super::reference::accepts(&ir, &candidate)
4190                || super::super::reference::can_continue(&ir, &candidate);
4191            for &id in ids {
4192                assert_eq!(forward_mask.get(TokenId(id)), expected, "{bytes:?}");
4193            }
4194        }
4195        assert!(forward.advance(b"\"}").expect("mask rolled back"));
4196        assert!(forward.is_accepting());
4197    }
4198
4199    #[test]
4200    fn shared_program_matchers_are_independent() {
4201        let program = program(r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true}"#);
4202        let mut first = program.new_matcher().expect("first");
4203        let second = program.new_matcher().expect("second");
4204        assert!(first.advance(b"[").expect("advance"));
4205        assert!(!first.is_dead());
4206        assert!(!second.is_accepting());
4207        assert!(!second.is_dead());
4208    }
4209
4210    #[cfg(feature = "bench-internals")]
4211    #[test]
4212    fn mask_work_metrics_count_actual_walk_and_reset_per_operation() {
4213        let program = program(r#"{"type":"string","pattern":"^a+$"}"#);
4214        let mut tokens = FxHashMap::default();
4215        for (id, bytes) in [
4216            (0u32, b"a".as_slice()),
4217            (1, b"aa"),
4218            (2, b"b"),
4219            (3, b"\""),
4220            (4, b"\\"),
4221        ] {
4222            tokens.insert(bytes.to_vec(), vec![id]);
4223        }
4224        let vocabulary =
4225            VocabularyHandle::new(Arc::new(build_vocabulary(5, tokens).expect("vocabulary")))
4226                .expect("handle");
4227        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4228        let mut matcher = program.new_matcher().expect("matcher");
4229        assert!(matcher.advance(b"\"").expect("open string"));
4230        let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4231
4232        matcher
4233            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4234            .expect("first mask");
4235        let first = matcher.mask_work_metrics_for_bench();
4236        assert!(first.trie_nodes > 0);
4237        assert!(first.trie_edges > 0);
4238        assert_eq!(first.pushed_bytes, first.trie_edges);
4239        assert_eq!(first.checkpoints, first.trie_nodes.saturating_add(1));
4240        assert_eq!(first.rollbacks, first.checkpoints);
4241
4242        matcher
4243            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4244            .expect("second mask");
4245        assert_eq!(matcher.mask_work_metrics_for_bench(), first);
4246    }
4247
4248    #[cfg(feature = "bench-internals")]
4249    #[test]
4250    fn mask_work_metrics_are_isolated_between_matchers() {
4251        let program = program(
4252            r##"{"$dynamicAnchor":"node","type":"object","properties":{"child":{"$dynamicRef":"#node"}}}"##,
4253        );
4254        let mut tokens = FxHashMap::default();
4255        tokens.insert(b"{".to_vec(), vec![0]);
4256        tokens.insert(b"}".to_vec(), vec![1]);
4257        let vocabulary =
4258            VocabularyHandle::new(Arc::new(build_vocabulary(2, tokens).expect("vocabulary")))
4259                .expect("handle");
4260        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4261        let mut first = program.new_matcher().expect("first");
4262        let second = program.new_matcher().expect("second");
4263        assert!(first.advance(br#"{"child":"#).expect("child prefix"));
4264        let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4265
4266        first
4267            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4268            .expect("dynamic mask");
4269        let first_metrics = first.mask_work_metrics_for_bench();
4270        assert!(first_metrics.dynamic_resolutions > 0);
4271        assert!(first_metrics.dynamic_scope_steps > 0);
4272        first
4273            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4274            .expect("warm dynamic mask");
4275        let warm_metrics = first.mask_work_metrics_for_bench();
4276        assert!(warm_metrics.dynamic_resolutions > 0);
4277        assert_eq!(warm_metrics.dynamic_scope_steps, 0);
4278        assert_eq!(
4279            second.mask_work_metrics_for_bench(),
4280            MaskWorkMetrics::default()
4281        );
4282    }
4283
4284    /// Every byte prefix of a valid document vs the reference oracle: exact completeness agreement,
4285    /// no over-acceptance, and the real next byte always allowed (no under-acceptance on valid input).
4286    #[test]
4287    fn incremental_matcher_agrees_with_the_reference_oracle_across_every_prefix() {
4288        let cases: &[(&str, &[u8])] = &[
4289            (
4290                r#"{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"boolean"}}}"#,
4291                br#"{"a":{"b":true},"c":{"d":false}}"#,
4292            ),
4293            (
4294                r#"{"type":"object","patternProperties":{"^x":{"type":"integer"}},"additionalProperties":{"type":"string"}}"#,
4295                br#"{"x1":5,"other":"ok"}"#,
4296            ),
4297            (
4298                r#"{"type":"object","propertyNames":{"type":"string","pattern":"^a$"},"additionalProperties":true}"#,
4299                br#"{"a":1}"#,
4300            ),
4301            (
4302                r#"{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":4}"#,
4303                br#"[1,22,333]"#,
4304            ),
4305            (r#"{"type":"string","pattern":"^a.c$"}"#, br#""abc""#),
4306            (
4307                r#"{"type":"object","minProperties":1,"maxProperties":2,"additionalProperties":{"type":"boolean"}}"#,
4308                br#"{"p":true,"q":false}"#,
4309            ),
4310            (
4311                r#"{"type":"object","dependentSchemas":{"a":{"type":"object","required":["b"],"additionalProperties":true}},"additionalProperties":true}"#,
4312                br#"{"b":2,"\u0061":1}"#,
4313            ),
4314            (
4315                r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
4316                br#"{"b":2,"\u0061":1}"#,
4317            ),
4318            (
4319                r#"{"dependentSchemas":{"a":false}}"#,
4320                br#"[null,true,{"a":1}]"#,
4321            ),
4322        ];
4323        let alphabet: Vec<u8> = (0u8..=127).collect();
4324        let mut prefix_count = 0usize;
4325        let mut candidate_byte_count = 0usize;
4326        for (schema, doc) in cases {
4327            let ir = ir(schema);
4328            let program = StructuredProgram::compile(ir.clone()).expect("program");
4329            assert_eq!(
4330                program.backend_kind(),
4331                "incremental",
4332                "{schema} must be incremental"
4333            );
4334            let records: Vec<(Vec<u8>, Vec<u32>)> = alphabet
4335                .iter()
4336                .enumerate()
4337                .map(|(i, &b)| (vec![b], vec![i as u32]))
4338                .collect();
4339            let mut m = program.new_matcher().expect("matcher");
4340            for end in 0..doc.len() {
4341                prefix_count += 1;
4342                candidate_byte_count += alphabet.len();
4343                let prefix = &doc[..end];
4344                assert_eq!(
4345                    m.is_accepting(),
4346                    super::super::reference::accepts(&ir, prefix),
4347                    "is_accepting diverged at {prefix:?} for {schema}"
4348                );
4349                let msk = mask(&mut m, alphabet.len(), &records);
4350                for (i, &b) in alphabet.iter().enumerate() {
4351                    if msk.get(TokenId(i as u32)) {
4352                        let mut cand = prefix.to_vec();
4353                        cand.push(b);
4354                        let oracle = super::super::reference::accepts(&ir, &cand)
4355                            || super::super::reference::can_continue(&ir, &cand);
4356                        assert!(
4357                            oracle,
4358                            "over-acceptance: byte {b:#04x} allowed at {prefix:?} but the oracle forbids it, {schema}"
4359                        );
4360                    }
4361                }
4362                let next = doc[end];
4363                assert!(
4364                    msk.get(TokenId(usize::from(next) as u32)),
4365                    "under-acceptance: valid next byte {next:#04x} masked out at {prefix:?}, {schema}"
4366                );
4367                assert!(
4368                    m.advance(&[next]).expect("advance"),
4369                    "valid byte {next:#04x} rejected at {prefix:?}, {schema}"
4370                );
4371            }
4372            assert!(
4373                m.is_accepting(),
4374                "complete valid document not accepting: {schema}"
4375            );
4376        }
4377        println!(
4378            "every-prefix differential: prefixes={prefix_count} candidate_bytes={candidate_byte_count}"
4379        );
4380    }
4381
4382    #[test]
4383    fn uniqueitems_speculative_mask_must_not_crash() {
4384        let program = program(
4385            r#"{"type":"array","items":{"type":"number"},"uniqueItems":true,"contains":{"const":1}}"#,
4386        );
4387        let mut m = program.new_matcher().expect("matcher");
4388        assert!(m.advance(b"[").expect("advance"));
4389        assert!(m.advance(b"1").expect("advance"));
4390        let mut records: Vec<(Vec<u8>, Vec<u32>)> = (0u8..=127)
4391            .map(|byte| (vec![byte], vec![u32::from(byte)]))
4392            .collect();
4393        records.push((b" ]".to_vec(), vec![127]));
4394        for _ in 0..100 {
4395            m.allowed_mask_from_records(
4396                128,
4397                records
4398                    .iter()
4399                    .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
4400            )
4401            .expect("speculative mask");
4402        }
4403        assert!(!m.advance(b"x").expect("invalid token"));
4404        assert!(m.advance(b"\t").expect("whitespace"));
4405        assert!(m.advance(b"]").expect("close"));
4406        assert!(m.is_accepting());
4407    }
4408
4409    #[cfg(feature = "bench-internals")]
4410    #[test]
4411    fn duplicate_finite_string_item_is_rejected_at_its_first_impossible_byte() {
4412        let base_program = program(
4413            r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
4414        );
4415        let tokens = (0u8..=255)
4416            .map(|byte| (vec![byte], vec![u32::from(byte)]))
4417            .collect();
4418        let vocabulary = VocabularyHandle::new(Arc::new(
4419            build_vocabulary(256, tokens).expect("byte vocabulary"),
4420        ))
4421        .expect("vocabulary handle");
4422        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4423
4424        for (name, second_prefix, rejected) in [
4425            ("raw", b"\"".as_slice(), b't'),
4426            ("escaped", b"\"\\u007".as_slice(), b'4'),
4427        ] {
4428            let mut prefix = b"[\"treasury\",".to_vec();
4429            prefix.extend_from_slice(second_prefix);
4430            let (mask, _) = assert_three_route_mask_and_advance(
4431                &base_program,
4432                &vocabulary,
4433                &trie,
4434                &prefix,
4435                name,
4436            );
4437            assert_eq!(
4438                mask[usize::from(rejected) / 8] & (1 << (rejected % 8)),
4439                0,
4440                "{name}: first byte completing the duplicate must be masked"
4441            );
4442
4443            let mut direct = base_program.new_matcher().expect("direct matcher");
4444            assert!(direct.advance(&prefix).expect("prefix"));
4445            assert!(!direct.advance(&[rejected]).expect("duplicate byte"));
4446            assert!(!direct.is_dead(), "rejection must preserve committed state");
4447        }
4448
4449        let distinct = b"[\"treasury\",\"risk";
4450        let (mask, _) = assert_three_route_mask_and_advance(
4451            &base_program,
4452            &vocabulary,
4453            &trie,
4454            distinct,
4455            "distinct",
4456        );
4457        assert_ne!(
4458            mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4459            0,
4460            "distinct item must remain closable"
4461        );
4462
4463        let nested = program(
4464            r#"{"type":"object","properties":{"approvals":{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}},"required":["approvals"],"unevaluatedProperties":false}"#,
4465        );
4466        let nested_prefix = b"{\"approvals\":[\"tr\\u0065\\u0061\\u0073\\u0075\\u0072\\u0079\",\"";
4467        let (mask, _) = assert_three_route_mask_and_advance(
4468            &nested,
4469            &vocabulary,
4470            &trie,
4471            nested_prefix,
4472            "nested-payment-shape",
4473        );
4474        assert_eq!(
4475            mask[usize::from(b't') / 8] & (1 << (b't' % 8)),
4476            0,
4477            "nested payment-shaped array must reject the duplicate at t"
4478        );
4479
4480        let escape_program =
4481            program(r#"{"type":"array","items":{"enum":["tram","tree"]},"uniqueItems":true}"#);
4482        let escape_prefix = b"[\"tree\",\"tr\\u006";
4483        let (mask, _) = assert_three_route_mask_and_advance(
4484            &escape_program,
4485            &vocabulary,
4486            &trie,
4487            escape_prefix,
4488            "pending-enum-escape",
4489        );
4490        assert_eq!(
4491            mask[usize::from(b'5') / 8] & (1 << (b'5' % 8)),
4492            0,
4493            "escape resolving only to the seen `tree` must be rejected"
4494        );
4495        assert_ne!(
4496            mask[usize::from(b'1') / 8] & (1 << (b'1' % 8)),
4497            0,
4498            "escape resolving to unseen `tram` must remain available"
4499        );
4500
4501        let completed_member = b"[\"treasury";
4502        let (mask, _) = assert_three_route_mask_and_advance(
4503            &base_program,
4504            &vocabulary,
4505            &trie,
4506            completed_member,
4507            "completed-enum-member",
4508        );
4509        assert_ne!(
4510            mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4511            0,
4512            "a complete first item must retain its closing quote"
4513        );
4514        assert_eq!(
4515            mask[usize::from(b'\\') / 8] & (1 << (b'\\' % 8)),
4516            0,
4517            "an enum member with no longer completion must reject a new escape"
4518        );
4519
4520        let before_third_item = b"[\"treasury\",\"risk\",";
4521        let (mask, _) = assert_three_route_mask_and_advance(
4522            &base_program,
4523            &vocabulary,
4524            &trie,
4525            before_third_item,
4526            "before-third-enum-item",
4527        );
4528        assert_ne!(
4529            mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4530            0,
4531            "an unseen member must permit the next opening quote"
4532        );
4533
4534        let third_item = b"[\"treasury\",\"risk\",\"";
4535        let (mask, _) = assert_three_route_mask_and_advance(
4536            &base_program,
4537            &vocabulary,
4538            &trie,
4539            third_item,
4540            "third-enum-item",
4541        );
4542        assert_eq!(
4543            mask[usize::from(b'!') / 8] & (1 << (b'!' % 8)),
4544            0,
4545            "a third enum item must reject a byte outside every remaining enum member"
4546        );
4547        assert_ne!(
4548            mask[usize::from(b'o') / 8] & (1 << (b'o' % 8)),
4549            0,
4550            "a third enum item must retain the unseen `ops` prefix"
4551        );
4552        for byte in [b'r', b't'] {
4553            assert_eq!(
4554                mask[usize::from(byte) / 8] & (1 << (byte % 8)),
4555                0,
4556                "a third enum item must reject seen member prefix {byte:?}"
4557            );
4558        }
4559
4560        let exhausted = program(
4561            r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"uniqueItems":true,"maxItems":4}"#,
4562        );
4563        let mut exhausted_matcher = exhausted.new_matcher().expect("exhausted matcher");
4564        assert!(exhausted_matcher
4565            .advance(br#"["treasury","risk","ops""#)
4566            .unwrap());
4567        assert!(!exhausted_matcher.advance(b",").unwrap());
4568        assert!(!exhausted_matcher.is_dead());
4569        assert!(exhausted_matcher.advance(b"]").unwrap());
4570        assert!(exhausted_matcher.is_accepting());
4571
4572        let wrapped = program(
4573            r#"{
4574                "type":"object",
4575                "properties":{
4576                    "rail":{"enum":["ach","wire","internal"]},
4577                    "approvals":{
4578                        "type":"array",
4579                        "items":{"enum":["treasury","ops","risk"]},
4580                        "contains":{"const":"treasury"},
4581                        "uniqueItems":true,
4582                        "minItems":1,
4583                        "maxItems":3
4584                    }
4585                },
4586                "required":["approvals"],
4587                "if":{"properties":{"rail":{"const":"wire"}},"required":["rail"]},
4588                "then":{"required":["rail"]},
4589                "else":{"not":{"required":["rail"]}},
4590                "allOf":[
4591                    {"if":{"properties":{"rail":{"const":"internal"}},"required":["rail"]},"then":{"required":["rail"]}},
4592                    {"if":{"properties":{"rail":{"const":"ach"}},"required":["rail"]},"then":{"required":["rail"]}}
4593                ],
4594                "unevaluatedProperties":false
4595            }"#,
4596        );
4597        for (spelling, treasury, risk) in [
4598            ("raw-raw", b"treasury".as_slice(), b"risk".as_slice()),
4599            (
4600                "escaped-raw",
4601                br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4602                b"risk".as_slice(),
4603            ),
4604            (
4605                "raw-escaped",
4606                b"treasury".as_slice(),
4607                br"r\u0069\u0073\u006B".as_slice(),
4608            ),
4609            (
4610                "escaped-escaped",
4611                br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4612                br"r\u0069\u0073\u006B".as_slice(),
4613            ),
4614        ] {
4615            for (scope, case, head, tail) in [
4616                (
4617                    "direct",
4618                    &base_program,
4619                    b"[\"".as_slice(),
4620                    br#"ops"]"#.as_slice(),
4621                ),
4622                (
4623                    "wrapper",
4624                    &wrapped,
4625                    b"{\"approvals\":[\"".as_slice(),
4626                    br#"ops"]}"#.as_slice(),
4627                ),
4628            ] {
4629                let label = format!("{scope}-{spelling}");
4630                let mut prefix = head.to_vec();
4631                prefix.extend_from_slice(treasury);
4632                prefix.extend_from_slice(b"\",\"");
4633                prefix.extend_from_slice(risk);
4634                prefix.extend_from_slice(b"\",\"");
4635                let (mask, _) =
4636                    assert_three_route_mask_and_advance(case, &vocabulary, &trie, &prefix, &label);
4637                let bit = |byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4638                assert!(bit(b'o'), "{label}: `ops` must remain unseen");
4639                for rejected in [b't', b'r', b'!'] {
4640                    assert!(!bit(rejected), "{label}: rejected byte {rejected:?}");
4641                }
4642
4643                let mut witness = prefix;
4644                witness.extend_from_slice(tail);
4645                let mut one_call = case.new_matcher().expect("one-call matcher");
4646                assert!(one_call.advance(&witness).unwrap(), "{label}: witness");
4647                assert!(one_call.is_accepting(), "{label}: accepting");
4648
4649                let mut bytewise = case.new_matcher().expect("bytewise matcher");
4650                for (index, &byte) in witness.iter().enumerate() {
4651                    assert!(
4652                        bytewise.advance(&[byte]).unwrap(),
4653                        "{label}: bytewise witness rejected {byte:?} at {index}"
4654                    );
4655                }
4656                assert!(bytewise.is_accepting(), "{label}: bytewise accepting");
4657
4658                // The release gate exercises the expensive mask-before-every-byte schedule. The
4659                // debug-focused test keeps the same logical matrix without multiplying runtime.
4660                #[cfg(not(debug_assertions))]
4661                {
4662                    let mut masked = case.new_matcher().expect("masked byte matcher");
4663                    for (index, &byte) in witness.iter().enumerate() {
4664                        let mut next = vec![0xa5; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4665                        masked
4666                            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut next)
4667                            .unwrap();
4668                        assert!(
4669                            next[usize::from(byte) / 8] & (1 << (byte % 8)) != 0,
4670                            "{label}: witness byte {byte:?} masked at {index}"
4671                        );
4672                        assert!(masked.advance(&[byte]).unwrap());
4673                    }
4674                    assert!(masked.is_accepting(), "{label}: masked replay accepting");
4675                }
4676            }
4677        }
4678        let wrapped_prefix = b"{\"approvals\":[\"treasury\",\"risk\",\"";
4679        let (mask, _) = assert_three_route_mask_and_advance(
4680            &wrapped,
4681            &vocabulary,
4682            &trie,
4683            wrapped_prefix,
4684            "wrapped-third-enum-item",
4685        );
4686        assert_ne!(
4687            mask[usize::from(b'o') / 8] & (1 << (b'o' % 8)),
4688            0,
4689            "wrappers must retain the remaining unseen `ops` member"
4690        );
4691        assert_eq!(
4692            mask[usize::from(b'!') / 8] & (1 << (b'!' % 8)),
4693            0,
4694            "wrappers must reject a prefix outside the remaining enum language"
4695        );
4696    }
4697
4698    #[cfg(feature = "bench-internals")]
4699    #[test]
4700    fn payment_approval_enum_retains_unseen_member_after_escaped_items() {
4701        let payment = program(
4702            r##"{
4703                "$schema":"https://json-schema.org/draft/2020-12/schema",
4704                "$id":"https://example.test/payment-instruction",
4705                "type":"object",
4706                "unevaluatedProperties":false,
4707                "required":["rail","amountMinor","currency","approvals","allocation"],
4708                "properties":{
4709                    "rail":{"enum":["ach","wire","internal"]},
4710                    "amountMinor":{"type":"integer","multipleOf":100,"minimum":100,"maximum":900},
4711                    "currency":{"enum":["USD","EUR","RUB"],"not":{"const":"RUB"}},
4712                    "routingNumber":{"type":"string","pattern":"^[0-9]{4}$"},
4713                    "iban":{"type":"string","pattern":"^[A-Z]{2}[0-9]{4}$"},
4714                    "accountRef":{"type":"string","pattern":"^acct-[0-9]{2}$"},
4715                    "fxRate":{"type":"integer","minimum":1,"maximum":9},
4716                    "settlementDate":{"type":"string","pattern":"^20[0-9]{2}$"},
4717                    "approvals":{
4718                        "type":"array",
4719                        "items":{"enum":["treasury","ops","risk"]},
4720                        "contains":{"const":"treasury"},
4721                        "uniqueItems":true,
4722                        "minItems":1,
4723                        "maxItems":3
4724                    },
4725                    "allocation":{"$ref":"#/$defs/split"},
4726                    "tags":{
4727                        "type":"object",
4728                        "propertyNames":{"pattern":"^[a-z]{2,6}$"},
4729                        "patternProperties":{"^[a-z]{2,6}$":{"type":"string","maxLength":6}},
4730                        "additionalProperties":false,
4731                        "maxProperties":2
4732                    }
4733                },
4734                "dependentRequired":{"fxRate":["settlementDate"]},
4735                "if":{"properties":{"rail":{"const":"wire"}},"required":["rail"]},
4736                "then":{"required":["iban"]},
4737                "else":{"not":{"required":["iban"]}},
4738                "allOf":[
4739                    {"if":{"properties":{"rail":{"const":"internal"}},"required":["rail"]},"then":{"required":["accountRef"]}},
4740                    {"if":{"properties":{"rail":{"const":"ach"}},"required":["rail"]},"then":{"required":["routingNumber"]}}
4741                ],
4742                "$defs":{
4743                    "split":{
4744                        "$dynamicAnchor":"split",
4745                        "type":"object",
4746                        "additionalProperties":false,
4747                        "required":["pct"],
4748                        "properties":{
4749                            "pct":{"type":"integer","minimum":1,"maximum":99},
4750                            "sub":{"$dynamicRef":"#split"}
4751                        }
4752                    }
4753                }
4754            }"##,
4755        );
4756        let records = (0u8..=255)
4757            .map(|byte| (vec![byte], vec![u32::from(byte)]))
4758            .collect();
4759        let vocabulary = VocabularyHandle::new(Arc::new(
4760            build_vocabulary(256, records).expect("byte vocabulary"),
4761        ))
4762        .expect("vocabulary handle");
4763        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4764        let document_head = b"{\n\n\"amountMinor\":100,\n\n\"currency\":\"USD\",\n\n\"accountRef\":\"acct-11\",\n\n\"fxRate\":1,\n\n\"routingNumber\":\"1123\",\n\n\"rail\":\"ach\",\n\n\"settlementDate\":\"2016\",\n\n\"tags\":{\n\n\"amount\":\"USD\",\n\n\"routin\":\"acct-1\"\n\n},\n\n\"approvals\":[\n\n\"";
4765
4766        for (label, treasury, risk) in [
4767            ("raw-raw", b"treasury".as_slice(), b"risk".as_slice()),
4768            (
4769                "escaped-raw",
4770                br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4771                b"risk".as_slice(),
4772            ),
4773            (
4774                "raw-escaped",
4775                b"treasury".as_slice(),
4776                br"r\u0069\u0073\u006B".as_slice(),
4777            ),
4778            (
4779                "escaped-escaped",
4780                br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4781                br"r\u0069\u0073\u006B".as_slice(),
4782            ),
4783        ] {
4784            let mut prefix = document_head.to_vec();
4785            prefix.extend_from_slice(treasury);
4786            prefix.extend_from_slice(b"\",\"");
4787            prefix.extend_from_slice(risk);
4788            prefix.extend_from_slice(b"\",\"");
4789            let (mask, _) =
4790                assert_three_route_mask_and_advance(&payment, &vocabulary, &trie, &prefix, label);
4791            let bit = |byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4792            assert!(bit(b'o'), "{label}: unseen `ops` must remain reachable");
4793            for rejected in [b't', b'r', b'!'] {
4794                assert!(!bit(rejected), "{label}: rejected byte {rejected:?}");
4795            }
4796
4797            let mut bytewise = payment.new_matcher().expect("bytewise payment matcher");
4798            for (index, &byte) in prefix.iter().enumerate() {
4799                assert!(
4800                    bytewise.advance(&[byte]).unwrap(),
4801                    "{label}: bytewise prefix rejected index {index}, byte {byte:?}, after {}",
4802                    String::from_utf8_lossy(&prefix[..index])
4803                );
4804            }
4805
4806            let mut witness = prefix;
4807            witness.extend_from_slice(br#"ops"],"allocation":{"pct":60}}"#);
4808            let mut matcher = payment.new_matcher().expect("payment matcher");
4809            assert!(matcher.advance(&witness).unwrap(), "{label}: witness");
4810            assert!(matcher.is_accepting(), "{label}: final payment");
4811        }
4812    }
4813
4814    #[cfg(feature = "bench-internals")]
4815    #[test]
4816    fn pending_scalar_bytes_preserve_pattern_liveness() {
4817        let records = (0u8..=255)
4818            .map(|byte| (vec![byte], vec![u32::from(byte)]))
4819            .collect();
4820        let vocabulary = VocabularyHandle::new(Arc::new(
4821            build_vocabulary(256, records).expect("byte vocabulary"),
4822        ))
4823        .expect("vocabulary handle");
4824        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4825        let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4826        let digits = *b"0123456789abcdefABCDEF";
4827        let escaped = program(r#"{"type":"string","pattern":"^ac[0-9]{2}$"}"#);
4828        for (label, prefix, expected) in [
4829            ("escape-start", br#""a\u"#.as_slice(), b"0".as_slice()),
4830            ("escape-zero", br#""a\u0"#.as_slice(), b"0".as_slice()),
4831            ("escape-byte", br#""a\u00"#.as_slice(), b"6".as_slice()),
4832            ("escape-nibble", br#""a\u006"#.as_slice(), b"3".as_slice()),
4833        ] {
4834            let (mask, _) =
4835                assert_three_route_mask_and_advance(&escaped, &vocabulary, &trie, prefix, label);
4836            for digit in digits {
4837                assert_eq!(
4838                    bit(&mask, digit),
4839                    expected.contains(&digit),
4840                    "{label}: hex byte {digit:?}"
4841                );
4842            }
4843            assert!(mask.iter().any(|byte| *byte != 0), "{label}: empty mask");
4844        }
4845        let mut dead_escape = escaped.new_matcher().expect("escaped matcher");
4846        assert!(dead_escape.advance(br#""a\u00"#).unwrap());
4847        assert!(!dead_escape.advance(b"a").unwrap());
4848        assert!(!dead_escape.is_dead());
4849
4850        let already_complete = program(r#"{"type":"string","pattern":"^ac[0-9]{2}$"}"#);
4851        let prefix = br#""ac11"#;
4852        let (mask, _) = assert_three_route_mask_and_advance(
4853            &already_complete,
4854            &vocabulary,
4855            &trie,
4856            prefix,
4857            "complete-pattern-cannot-open-an-extra-escape",
4858        );
4859        assert!(bit(&mask, b'"'));
4860        assert!(!bit(&mask, b'\\'));
4861        let mut rejected_surrogate = already_complete
4862            .new_matcher()
4863            .expect("surrogate rejection matcher");
4864        assert!(rejected_surrogate.advance(prefix).unwrap());
4865        assert!(!rejected_surrogate.advance(b"\\").unwrap());
4866        assert!(!rejected_surrogate.is_dead());
4867
4868        let supplementary = program(r#"{"type":"string","pattern":"^a😀$"}"#);
4869        let (mask, _) = assert_three_route_mask_and_advance(
4870            &supplementary,
4871            &vocabulary,
4872            &trie,
4873            br#""a\u"#,
4874            "live-surrogate-start",
4875        );
4876        assert!(bit(&mask, b'D') && bit(&mask, b'd'));
4877        assert!(!bit(&mask, b'0'));
4878        let mut supplementary_witness = supplementary
4879            .new_matcher()
4880            .expect("supplementary witness matcher");
4881        assert!(supplementary_witness
4882            .advance(br#""a\uD83D\uDE00""#)
4883            .unwrap());
4884        assert!(supplementary_witness.is_accepting());
4885
4886        for (label, schema, prefix) in [
4887            (
4888                "unconstrained-string-escape",
4889                r#"{"type":"string"}"#,
4890                br#""a\u"#.as_slice(),
4891            ),
4892            (
4893                "length-headroom-escape",
4894                r#"{"type":"string","maxLength":5}"#,
4895                br#""ab\u"#.as_slice(),
4896            ),
4897            (
4898                "single-scalar-pattern-escape",
4899                r#"{"type":"string","pattern":"^a.$"}"#,
4900                br#""a\u"#.as_slice(),
4901            ),
4902            (
4903                "open-object-key-escape",
4904                r#"{"type":"object"}"#,
4905                br#"{"a\u"#.as_slice(),
4906            ),
4907            (
4908                "plain-array-string-escape",
4909                r#"{"type":"array","items":{"type":"string"}}"#,
4910                br#"["a\u"#.as_slice(),
4911            ),
4912        ] {
4913            let control = program(schema);
4914            let (mask, _) =
4915                assert_three_route_mask_and_advance(&control, &vocabulary, &trie, prefix, label);
4916            for digit in digits {
4917                assert!(bit(&mask, digit), "{label}: rejected {digit:?}");
4918            }
4919        }
4920
4921        let bmp_enum = program(r#"{"type":"array","items":{"enum":["c","d"]},"uniqueItems":true}"#);
4922        let (mask, _) = assert_three_route_mask_and_advance(
4923            &bmp_enum,
4924            &vocabulary,
4925            &trie,
4926            br#"["\u"#,
4927            "bmp-enum-surrogate-rejection",
4928        );
4929        for digit in digits {
4930            assert_eq!(bit(&mask, digit), digit == b'0', "BMP enum: {digit:?}");
4931        }
4932
4933        let supplementary_enum =
4934            program(r#"{"type":"array","items":{"enum":["\ud83d\ude00","x"]},"uniqueItems":true}"#);
4935        let (mask, _) = assert_three_route_mask_and_advance(
4936            &supplementary_enum,
4937            &vocabulary,
4938            &trie,
4939            br#"["\u"#,
4940            "supplementary-enum-surrogate-reachable",
4941        );
4942        assert!(bit(&mask, b'D') && bit(&mask, b'd'));
4943
4944        let raw = program(r#"{"type":"string","pattern":"^aé$"}"#);
4945        let (mask, _) =
4946            assert_three_route_mask_and_advance(&raw, &vocabulary, &trie, b"\"a", "raw-utf8-lead");
4947        assert!(bit(&mask, 0xc3));
4948        assert!(!bit(&mask, 0xc2));
4949        let (mask, _) = assert_three_route_mask_and_advance(
4950            &raw,
4951            &vocabulary,
4952            &trie,
4953            b"\"a\xc3",
4954            "raw-utf8-continuation",
4955        );
4956        assert!(bit(&mask, 0xa9));
4957        assert!(!bit(&mask, 0xa8));
4958    }
4959
4960    #[cfg(feature = "bench-internals")]
4961    #[test]
4962    fn object_key_partial_scalars_and_pattern_unions_preserve_liveness() {
4963        let records = (0u8..=255)
4964            .map(|byte| (vec![byte], vec![u32::from(byte)]))
4965            .collect();
4966        let vocabulary = VocabularyHandle::new(Arc::new(
4967            build_vocabulary(256, records).expect("byte vocabulary"),
4968        ))
4969        .expect("vocabulary handle");
4970        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4971        let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4972        let digits = *b"0123456789abcdefABCDEF";
4973
4974        let exact = program(
4975            r#"{"type":"object","properties":{"edges":{"type":"integer"},"force":{"type":"array","uniqueItems":true}},"additionalProperties":false}"#,
4976        );
4977        for (label, prefix) in [
4978            ("exact-first-escape", br#"{"e\u"#.as_slice()),
4979            ("exact-second-escape", br#"{"ed\u"#.as_slice()),
4980        ] {
4981            let (mask, _) =
4982                assert_three_route_mask_and_advance(&exact, &vocabulary, &trie, prefix, label);
4983            for digit in digits {
4984                assert_eq!(bit(&mask, digit), digit == b'0', "{label}: {digit:?}");
4985            }
4986        }
4987
4988        let named_pattern = program(
4989            r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2}$"},"patternProperties":{"^[a-z]{2}$":{"type":"integer"}},"additionalProperties":false}"#,
4990        );
4991        let (mask, _) = assert_three_route_mask_and_advance(
4992            &named_pattern,
4993            &vocabulary,
4994            &trie,
4995            br#"{"a\u"#,
4996            "property-name-and-pattern",
4997        );
4998        for digit in digits {
4999            assert_eq!(bit(&mask, digit), digit == b'0', "pattern key: {digit:?}");
5000        }
5001
5002        let no_keys = program(
5003            r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2}$"},"additionalProperties":false}"#,
5004        );
5005        let (mask, _) = assert_three_route_mask_and_advance(
5006            &no_keys,
5007            &vocabulary,
5008            &trie,
5009            b"{",
5010            "empty-key-language",
5011        );
5012        assert!(!bit(&mask, b'"'));
5013        assert!(bit(&mask, b'}'));
5014
5015        let two_patterns = program(
5016            r#"{"type":"object","patternProperties":{"^a[a-z]$":{"type":"integer"},"^b[a-z]$":{"type":"integer"}},"additionalProperties":false}"#,
5017        );
5018        let (mask, _) = assert_three_route_mask_and_advance(
5019            &two_patterns,
5020            &vocabulary,
5021            &trie,
5022            br#"{""#,
5023            "two-pattern-union",
5024        );
5025        assert!(bit(&mask, b'a') && bit(&mask, b'b') && bit(&mask, b'\\'));
5026        assert!(!bit(&mask, b'z'));
5027
5028        let unevaluated_patterns = program(
5029            r#"{"type":"object","patternProperties":{"^a[a-z]$":{"type":"integer"},"^b[a-z]$":{"type":"integer"}},"unevaluatedProperties":false}"#,
5030        );
5031        let (mask, _) = assert_three_route_mask_and_advance(
5032            &unevaluated_patterns,
5033            &vocabulary,
5034            &trie,
5035            br#"{""#,
5036            "unevaluated-two-pattern-union",
5037        );
5038        assert!(bit(&mask, b'a') && bit(&mask, b'b'));
5039        assert!(!bit(&mask, b'z'));
5040
5041        let covered_by_false_pattern = program(
5042            r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^a.$":false},"additionalProperties":false}"#,
5043        );
5044        let (mask, _) = assert_three_route_mask_and_advance(
5045            &covered_by_false_pattern,
5046            &vocabulary,
5047            &trie,
5048            b"{",
5049            "positive-language-covered-by-false-pattern",
5050        );
5051        assert!(!bit(&mask, b'"'));
5052        assert!(bit(&mask, b'}'));
5053
5054        let partially_subtracted_pattern = program(
5055            r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^ab$":false},"additionalProperties":false}"#,
5056        );
5057        let (mask, _) = assert_three_route_mask_and_advance(
5058            &partially_subtracted_pattern,
5059            &vocabulary,
5060            &trie,
5061            br#"{"a"#,
5062            "positive-language-minus-one-false-key",
5063        );
5064        assert!(!bit(&mask, b'b'));
5065        assert!(bit(&mask, b'c'));
5066        let (mask, _) = assert_three_route_mask_and_advance(
5067            &partially_subtracted_pattern,
5068            &vocabulary,
5069            &trie,
5070            br#"{"aa"#,
5071            "complete-key-cannot-open-an-extra-escape",
5072        );
5073        assert!(bit(&mask, b'"'));
5074        assert!(!bit(&mask, b'\\'));
5075
5076        let unevaluated_items =
5077            program(r#"{"type":"array","prefixItems":[{"enum":["p"]}],"unevaluatedItems":false}"#);
5078        let (mask, _) = assert_three_route_mask_and_advance(
5079            &unevaluated_items,
5080            &vocabulary,
5081            &trie,
5082            br#"["p""#,
5083            "unevaluated-items-exhausted",
5084        );
5085        assert!(bit(&mask, b']'));
5086        assert!(!bit(&mask, b','));
5087
5088        let ascii_records: FxHashMap<Vec<u8>, Vec<u32>> = (0x20u8..=0x7e)
5089            .chain([b'\t', b'\n', b'\r'])
5090            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5091            .collect();
5092        let ascii_vocabulary = VocabularyHandle::new(Arc::new(
5093            build_vocabulary(256, ascii_records).expect("ASCII vocabulary"),
5094        ))
5095        .expect("ASCII vocabulary handle");
5096        let ascii_trie = TrieCache::new()
5097            .bind(&ascii_vocabulary)
5098            .expect("ASCII trie");
5099        let (ascii_mask, _) = assert_three_route_mask_and_advance(
5100            &unevaluated_items,
5101            &ascii_vocabulary,
5102            &ascii_trie,
5103            br#"["p""#,
5104            "unevaluated-items-sparse-vocabulary",
5105        );
5106        assert!(!bit(&ascii_mask, b','));
5107        let mut whole = unevaluated_items
5108            .new_matcher()
5109            .expect("whole-token unevaluatedItems matcher");
5110        assert!(!whole.advance(br#"["p","#).unwrap());
5111    }
5112
5113    #[cfg(feature = "bench-internals")]
5114    #[test]
5115    fn arrays_numbers_and_negation_refuse_known_dead_prefixes() {
5116        let records = (0u8..=255)
5117            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5118            .collect();
5119        let vocabulary = VocabularyHandle::new(Arc::new(
5120            build_vocabulary(256, records).expect("byte vocabulary"),
5121        ))
5122        .expect("vocabulary handle");
5123        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5124        let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
5125
5126        let max_contains = program(
5127            r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5128        );
5129        let (mask, _) = assert_three_route_mask_and_advance(
5130            &max_contains,
5131            &vocabulary,
5132            &trie,
5133            br#"["p",""#,
5134            "max-contains-second-item-prefix",
5135        );
5136        assert!(!bit(&mask, b'p'));
5137        assert!(bit(&mask, b'q'));
5138        let mut direct = max_contains.new_matcher().expect("maxContains matcher");
5139        assert!(direct.advance(br#"["p",""#).unwrap());
5140        assert!(!direct.advance(b"p").unwrap());
5141
5142        let exclusive = program(r#"{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1}"#);
5143        let (mask, _) = assert_three_route_mask_and_advance(
5144            &exclusive,
5145            &vocabulary,
5146            &trie,
5147            b" ",
5148            "exclusive-unit-interval",
5149        );
5150        assert!(bit(&mask, b'0'));
5151        assert!(bit(&mask, b'1'));
5152        let mut exponent_witness = exclusive.new_matcher().expect("number witness");
5153        assert!(exponent_witness.advance(b"1e-1").unwrap());
5154        assert!(exponent_witness.is_accepting());
5155
5156        let negated_limit = program(r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#);
5157        let (mask, _) = assert_three_route_mask_and_advance(
5158            &negated_limit,
5159            &vocabulary,
5160            &trie,
5161            br#""ab"#,
5162            "negated-max-length",
5163        );
5164        assert!(!bit(&mask, b'c'));
5165
5166        let impossible_unique = program(
5167            r#"{"type":"array","items":{"enum":["p","q"]},"uniqueItems":true,"minItems":3,"maxItems":3}"#,
5168        );
5169        let (mask, _) = assert_three_route_mask_and_advance(
5170            &impossible_unique,
5171            &vocabulary,
5172            &trie,
5173            b"",
5174            "impossible-finite-unique-array",
5175        );
5176        assert!(!bit(&mask, b'['));
5177        for whitespace in b" \t\r\n" {
5178            assert!(!bit(&mask, *whitespace));
5179        }
5180    }
5181
5182    #[cfg(feature = "bench-internals")]
5183    #[test]
5184    fn realistic_depth_mask_advance_and_witness_reachability_corpus_gate() {
5185        struct Fixture {
5186            name: &'static str,
5187            schema: &'static str,
5188            witness: &'static [u8],
5189            selected_prefix: &'static [u8],
5190        }
5191
5192        let fixtures = [
5193            Fixture {
5194                name: "closed-exact-object",
5195                schema: r#"{"type":"object","properties":{"nodes":{"type":"array"},"edges":{"type":"array"}},"required":["nodes","edges"],"additionalProperties":false}"#,
5196                witness: br#"{"nodes":[],"edges":[]}"#,
5197                selected_prefix: br#"{""#,
5198            },
5199            Fixture {
5200                name: "pydantic-defs-ref-nested-arrays",
5201                schema: r##"{"$defs":{"Node":{"type":"object","properties":{"id":{"type":"integer"}},"required":["id"],"additionalProperties":false},"Edge":{"type":"object","properties":{"source":{"type":"integer"},"target":{"type":"integer"}},"required":["source","target"],"additionalProperties":false}},"type":"object","properties":{"nodes":{"type":"array","items":{"$ref":"#/$defs/Node"}},"edges":{"type":"array","items":{"$ref":"#/$defs/Edge"}}},"required":["nodes","edges"],"additionalProperties":false}"##,
5202                witness: br#"{"nodes":[{"id":1}],"edges":[{"source":1,"target":2}]}"#,
5203                selected_prefix: br#"{"nodes":[{"id":1}],"edges":[{"source":1,"target":"#,
5204            },
5205            Fixture {
5206                name: "property-name-two-pattern-union",
5207                schema: r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2,6}$"},"patternProperties":{"^a[a-z]{1,5}$":{"type":"string"},"^r[a-z]{1,5}$":{"type":"string"}},"additionalProperties":false,"maxProperties":2}"#,
5208                witness: br#"{"amount":"USD","routin":"acct"}"#,
5209                selected_prefix: br#"{"amount":"USD","routin"#,
5210            },
5211            Fixture {
5212                name: "conditional-dependent-unevaluated-oneof",
5213                schema: r#"{"type":"object","properties":{"kind":{"enum":["a","b"]},"value":{"oneOf":[{"type":"integer"},{"type":"string","pattern":"^ok$"}]},"flag":{"type":"boolean"}},"required":["kind","value"],"dependentSchemas":{"flag":{"required":["value"]}},"allOf":[{"if":{"properties":{"kind":{"const":"a"}},"required":["kind"]},"then":{"not":{"properties":{"value":{"const":0}},"required":["value"]}},"else":{"anyOf":[{"required":["flag"]},{"not":{"required":["flag"]}}]}}],"unevaluatedProperties":false}"#,
5214                witness: br#"{"kind":"a","value":1,"flag":true}"#,
5215                selected_prefix: br#"{"kind":"a","value":1,"flag":t"#,
5216            },
5217            Fixture {
5218                name: "unique-contains-third-item",
5219                schema: r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
5220                witness: br#"["treasury","risk","ops"]"#,
5221                selected_prefix: br#"["treasury","risk",""#,
5222            },
5223            Fixture {
5224                name: "unevaluated-items-boundary",
5225                schema: r#"{"prefixItems":[{"enum":["p"]}],"contains":{"const":"q"},"unevaluatedItems":false}"#,
5226                witness: br#"["p","q"]"#,
5227                selected_prefix: br#"["p","q"#,
5228            },
5229            Fixture {
5230                name: "dynamic-reference-recursion",
5231                schema: r##"{"$dynamicAnchor":"node","type":"object","properties":{"value":{"type":"integer"},"child":{"$dynamicRef":"#node"}},"required":["value"],"additionalProperties":false}"##,
5232                witness: br#"{"value":1,"child":{"value":2,"child":{"value":3}}}"#,
5233                selected_prefix: br#"{"value":1,"child":{"value":2,"child":{"value":"#,
5234            },
5235            Fixture {
5236                name: "exclusive-decimal-multiple",
5237                schema: r#"{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1,"multipleOf":0.1}"#,
5238                witness: b"0.5",
5239                selected_prefix: b"0.",
5240            },
5241            Fixture {
5242                name: "max-contains-cap",
5243                schema: r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5244                witness: br#"["p","q"]"#,
5245                selected_prefix: br#"["p",""#,
5246            },
5247            Fixture {
5248                name: "negated-constant-at-max-length",
5249                schema: r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#,
5250                witness: br#""abd""#,
5251                selected_prefix: br#""ab"#,
5252            },
5253        ];
5254
5255        let mut records: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5256            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5257            .collect();
5258        records.insert(b"e".to_vec(), vec![u32::from(b'e'), 256]);
5259        records.insert(b"name".to_vec(), vec![257, 258]);
5260        records.insert(b"ops".to_vec(), vec![259, 260]);
5261        let vocabulary = VocabularyHandle::new(Arc::new(
5262            build_vocabulary(261, records).expect("corpus vocabulary"),
5263        ))
5264        .expect("corpus vocabulary handle");
5265        let trie = TrieCache::new().bind(&vocabulary).expect("corpus trie");
5266        let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5267
5268        for fixture in fixtures {
5269            assert!(fixture.witness.starts_with(fixture.selected_prefix));
5270            let case = program(fixture.schema);
5271            assert_three_route_mask_and_advance(
5272                &case,
5273                &vocabulary,
5274                &trie,
5275                fixture.selected_prefix,
5276                fixture.name,
5277            );
5278
5279            let mut matcher = case.new_matcher().expect("corpus matcher");
5280            for (index, &next) in fixture.witness.iter().enumerate() {
5281                let mut first = vec![0xa5; output_len];
5282                matcher
5283                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut first)
5284                    .unwrap_or_else(|error| panic!("{} prefix {index}: {error}", fixture.name));
5285                assert!(
5286                    first.iter().any(|byte| *byte != 0),
5287                    "{} produced an empty mask at valid prefix {:?}",
5288                    fixture.name,
5289                    &fixture.witness[..index]
5290                );
5291                assert!(
5292                    first[usize::from(next) / 8] & (1 << (next % 8)) != 0,
5293                    "{} masked witness byte {next:?} at prefix {:?}",
5294                    fixture.name,
5295                    &fixture.witness[..index]
5296                );
5297                assert!(
5298                    matcher.advance(&[next]).unwrap(),
5299                    "{} rejected witness byte {next:?} at {index}",
5300                    fixture.name
5301                );
5302                assert_incremental_accounting(&matcher, fixture.name);
5303            }
5304            assert!(matcher.is_accepting(), "{} witness", fixture.name);
5305        }
5306    }
5307
5308    #[cfg(feature = "bench-internals")]
5309    #[test]
5310    fn finite_admitted_frontier_successors_are_completable() {
5311        use std::collections::{HashSet, VecDeque};
5312
5313        let fixtures = [
5314            (
5315                "max-contains-frontier",
5316                r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5317                br#"["p",""#.as_slice(),
5318            ),
5319            (
5320                "unique-contains-frontier",
5321                r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
5322                br#"["treasury","risk",""#.as_slice(),
5323            ),
5324            (
5325                "pattern-difference-frontier",
5326                r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^ab$":false},"additionalProperties":false}"#,
5327                br#"{"a"#.as_slice(),
5328            ),
5329            (
5330                "negated-max-length-frontier",
5331                r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#,
5332                br#""ab"#.as_slice(),
5333            ),
5334        ];
5335        let records: FxHashMap<Vec<u8>, Vec<u32>> = (0x20u8..=0x7e)
5336            .chain([b'\t', b'\n', b'\r'])
5337            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5338            .collect();
5339        let vocabulary = VocabularyHandle::new(Arc::new(
5340            build_vocabulary(256, records).expect("ASCII liveness vocabulary"),
5341        ))
5342        .expect("ASCII liveness vocabulary handle");
5343        let trie = TrieCache::new()
5344            .bind(&vocabulary)
5345            .expect("ASCII liveness trie");
5346        let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5347        let fixture_filter = std::env::var("MF_LIVENESS_FIXTURE").ok();
5348
5349        for (label, schema, initial) in fixtures {
5350            if fixture_filter
5351                .as_deref()
5352                .is_some_and(|wanted| wanted != label)
5353            {
5354                continue;
5355            }
5356            let case = program(schema);
5357            let mut frontier = VecDeque::from([(initial.to_vec(), 0usize)]);
5358            let mut visited = HashSet::from([initial.to_vec()]);
5359            while let Some((prefix, frontier_depth)) = frontier.pop_front() {
5360                let mut matcher = case.new_matcher().expect("frontier matcher");
5361                assert!(matcher.advance(&prefix).unwrap(), "{label}: {prefix:?}");
5362                let mut output = vec![0xa5; output_len];
5363                matcher
5364                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
5365                    .unwrap_or_else(|error| panic!("{label} {prefix:?}: {error}"));
5366                for (token, ids) in vocabulary.iter_records() {
5367                    let admitted = ids
5368                        .iter()
5369                        .any(|id| output[*id as usize / 8] & (1 << (*id % 8)) != 0);
5370                    let mut candidate = prefix.clone();
5371                    candidate.extend_from_slice(token);
5372                    let mut exact = case.new_matcher().expect("frontier transition matcher");
5373                    assert_eq!(
5374                        admitted,
5375                        exact.advance(&candidate).unwrap(),
5376                        "{label}: mask/whole-token advance disagreement: {candidate:?}"
5377                    );
5378                }
5379                if std::env::var_os("MF_LIVENESS_TRANSITIONS_ONLY").is_some() {
5380                    continue;
5381                }
5382                for (token, ids) in vocabulary.iter_records() {
5383                    let admitted = ids
5384                        .iter()
5385                        .any(|id| output[*id as usize / 8] & (1 << (*id % 8)) != 0);
5386                    if !admitted {
5387                        continue;
5388                    }
5389                    let mut candidate = prefix.clone();
5390                    candidate.extend_from_slice(token);
5391                    let mut budget = 50_000;
5392                    assert_eq!(
5393                        closer_first_completion_search(&case, &candidate, 24, &mut budget),
5394                        CompletionSearch::Found,
5395                        "{label}: admitted successor has no proven completion: {candidate:?}"
5396                    );
5397                    if frontier_depth < 1 && visited.insert(candidate.clone()) {
5398                        frontier.push_back((candidate, frontier_depth + 1));
5399                    }
5400                }
5401                assert!(visited.len() <= 512, "{label}: unbounded test frontier");
5402            }
5403        }
5404    }
5405
5406    #[cfg(feature = "bench-internals")]
5407    #[test]
5408    fn closed_object_key_slice_never_emits_a_token_that_exact_advance_rejects() {
5409        // Leading whitespace covers a GPT-2 prefix with no declared property match.
5410        // `uniqueItems` elsewhere still selects the structured backend.
5411        let program = program(
5412            r#"{
5413                "type":"object",
5414                "properties":{
5415                    "productId":{"type":"integer"},
5416                    "tags":{"type":"array","items":{"type":"string"},"uniqueItems":true}
5417                },
5418                "required":["productId"],
5419                "additionalProperties":false
5420            }"#,
5421        );
5422        let mut tokens: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5423            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5424            .collect();
5425        tokens.insert(b"name".to_vec(), vec![256]);
5426        let vocabulary = VocabularyHandle::new(Arc::new(
5427            build_vocabulary(257, tokens).expect("key-slice vocabulary"),
5428        ))
5429        .expect("vocabulary handle");
5430        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5431        let (mask, _) = assert_three_route_mask_and_advance(
5432            &program,
5433            &vocabulary,
5434            &trie,
5435            b"\n{ \"",
5436            "closed-key-after-whitespace",
5437        );
5438        assert_eq!(
5439            mask[256 / 8] & (1 << (256 % 8)),
5440            0,
5441            "an impossible multi-byte key prefix must not be emitted"
5442        );
5443        assert_ne!(
5444            mask[usize::from(b'p') / 8] & (1 << (b'p' % 8)),
5445            0,
5446            "the viable `productId` prefix must remain"
5447        );
5448    }
5449
5450    #[cfg(feature = "bench-internals")]
5451    #[test]
5452    fn pydantic_knowledge_graph_root_key_mask_matches_every_transition() {
5453        let schema = program(
5454            r##"{
5455                "$defs":{
5456                    "Edge":{
5457                        "additionalProperties":false,
5458                        "description":"One directed relation between two entities.",
5459                        "properties":{
5460                            "source":{"description":"Unique source of the edge","maximum":9,"minimum":1,"title":"Source","type":"integer"},
5461                            "target":{"description":"Unique target of the edge","maximum":9,"minimum":1,"title":"Target","type":"integer"},
5462                            "label":{"description":"Label of the edge","maxLength":10,"title":"Label","type":"string"}
5463                        },
5464                        "required":["source","target","label"],
5465                        "title":"Edge",
5466                        "type":"object"
5467                    },
5468                    "Node":{
5469                        "additionalProperties":false,
5470                        "description":"One entity in the graph.",
5471                        "properties":{
5472                            "id":{"description":"Unique identifier of the node","maximum":9,"minimum":1,"title":"Id","type":"integer"},
5473                            "label":{"description":"Label of the node","maxLength":10,"title":"Label","type":"string"}
5474                        },
5475                        "required":["id","label"],
5476                        "title":"Node",
5477                        "type":"object"
5478                    }
5479                },
5480                "additionalProperties":false,
5481                "description":"A graph of entities and the relations between them.",
5482                "properties":{
5483                    "nodes":{"items":{"$ref":"#/$defs/Node"},"maxItems":3,"minItems":1,"title":"Nodes","type":"array"},
5484                    "edges":{"items":{"$ref":"#/$defs/Edge"},"maxItems":2,"minItems":1,"title":"Edges","type":"array"}
5485                },
5486                "required":["nodes","edges"],
5487                "title":"KnowledgeGraph",
5488                "type":"object"
5489            }"##,
5490        );
5491        let mut records: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5492            .map(|byte| (vec![byte], vec![u32::from(byte)]))
5493            .collect();
5494        for (id, token) in [
5495            (256, b"name".as_slice()),
5496            (257, b"nodes"),
5497            (259, b"edges"),
5498            (260, b"node"),
5499            (261, b"edge"),
5500            (262, b"nodes\""),
5501            (263, b"nodes\":"),
5502            (264, b"edges\":["),
5503            (265, b"\\u006e"),
5504            (266, b"\\u0065"),
5505            (267, b"\\u2"),
5506            (268, b"\":"),
5507            (269, b"nodes\":["),
5508            (270, b"n\\u006fdes\":"),
5509            (271, b"\xc3\xa9"),
5510        ] {
5511            records.insert(token.to_vec(), vec![id]);
5512        }
5513        records.get_mut(b"nodes".as_slice()).unwrap().push(258);
5514        records.get_mut(&[0xef][..]).unwrap().push(272);
5515        let vocabulary = VocabularyHandle::new(Arc::new(
5516            build_vocabulary(280, records).expect("knowledge graph vocabulary"),
5517        ))
5518        .expect("vocabulary handle");
5519        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5520        let prefix = b"{ \"";
5521        let (mask, _) = assert_three_route_mask_and_advance(
5522            &schema,
5523            &vocabulary,
5524            &trie,
5525            prefix,
5526            "knowledge-graph-root-key",
5527        );
5528        let bit = |id: u32| mask[id as usize / 8] & (1 << (id % 8)) != 0;
5529
5530        for byte in 0x20u8..=0x7e {
5531            assert_eq!(
5532                bit(u32::from(byte)),
5533                matches!(byte, b'n' | b'e' | b'\\'),
5534                "printable root key byte {byte:?}"
5535            );
5536        }
5537        for byte in 0u8..0x20 {
5538            assert!(!bit(u32::from(byte)), "control key byte {byte:#04x}");
5539        }
5540        assert!(!bit(256), "raw token `name` must be rejected");
5541        assert!(bit(257) && bit(258), "all aliases for `nodes` must agree");
5542        assert!(bit(259));
5543
5544        let mut decoder = super::super::lexer::JsonStringDecoder::new();
5545        assert_eq!(
5546            decoder.push(0xef),
5547            super::super::lexer::DecodeStep::Continue,
5548            "0xEF is a lexically pending UTF-8 lead byte"
5549        );
5550        assert!(!bit(0xef), "0xEF cannot reach either finite decoded key");
5551        assert!(!bit(272), "the 0xEF alias must agree");
5552
5553        for (token, ids) in vocabulary.iter_records() {
5554            let mut matcher = schema.new_matcher().expect("transition matcher");
5555            assert!(matcher.advance(prefix).unwrap());
5556            let before = (
5557                matcher.is_accepting(),
5558                matcher.is_dead(),
5559                matcher.eos_legal(),
5560            );
5561            let accepted = matcher.advance(token).unwrap();
5562            for id in ids {
5563                assert_eq!(bit(*id), accepted, "token {token:?}, id {id}");
5564            }
5565            if !accepted {
5566                assert_eq!(
5567                    (
5568                        matcher.is_accepting(),
5569                        matcher.is_dead(),
5570                        matcher.eos_legal(),
5571                    ),
5572                    before,
5573                    "rejected token mutated committed state: {token:?}"
5574                );
5575            }
5576            assert_incremental_accounting(&matcher, "knowledge-graph-transition");
5577        }
5578
5579        for document in [
5580            br#"{"nodes":[{"id":1,"label":"a"}],"edges":[{"source":1,"target":1,"label":"x"}]}"#
5581                .as_slice(),
5582            br#"{"edges":[{"source":1,"target":1,"label":"x"}],"nodes":[{"label":"a","id":1}]}"#
5583                .as_slice(),
5584        ] {
5585            let mut matcher = schema.new_matcher().expect("witness matcher");
5586            assert!(matcher.advance(document).unwrap());
5587            assert!(matcher.is_accepting());
5588            assert_incremental_accounting(&matcher, "knowledge-graph-witness");
5589        }
5590    }
5591
5592    #[test]
5593    fn unique_contains_trie_masks_are_transactional_after_each_committed_item() {
5594        let ir = ir(
5595            r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true,"contains":{"const":0}}"#,
5596        );
5597        let program = StructuredProgram::compile(ir.clone()).expect("program");
5598        let mut tokens = FxHashMap::default();
5599        for (id, bytes) in [
5600            (0, b"[".as_slice()),
5601            (1, b"0"),
5602            (2, b",1"),
5603            (3, b",2"),
5604            (4, b" "),
5605            (5, b","),
5606            (6, b"]"),
5607            (7, b",1]"),
5608            (8, b",3]"),
5609            (9, b"x"),
5610        ] {
5611            tokens.insert(bytes.to_vec(), vec![id]);
5612        }
5613        let handle = VocabularyHandle::new(Arc::new(build_vocabulary(10, tokens).expect("vocab")))
5614            .expect("handle");
5615        let trie = TrieCache::new().bind(&handle).expect("trie");
5616        let mut trie_matcher = program.new_matcher().expect("trie matcher");
5617        let mut record_matcher = program.new_matcher().expect("record matcher");
5618        let mut output = [0u8; 4];
5619
5620        for token in [b"[".as_slice(), b"0", b",1", b",2"] {
5621            assert!(trie_matcher.advance(token).expect("trie commit"));
5622            assert!(record_matcher.advance(token).expect("record commit"));
5623            for (bytes, ids) in handle.iter_records() {
5624                record_matcher
5625                    .allowed_mask_from_records(handle.mask_vocab_size(), [(bytes, ids)].into_iter())
5626                    .unwrap_or_else(|error| {
5627                        panic!("candidate {bytes:?} after {token:?}: {error:?}")
5628                    });
5629            }
5630            let expected = record_matcher
5631                .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
5632                .unwrap_or_else(|error| panic!("record mask after {token:?}: {error:?}"));
5633            for _ in 0..128 {
5634                trie_matcher
5635                    .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
5636                    .expect("trie mask");
5637            }
5638            for id in 0..handle.mask_vocab_size() {
5639                let bit = output[id / 8] & (1u8 << (id % 8)) != 0;
5640                assert_eq!(bit, expected.get(TokenId(u32::try_from(id).expect("id"))));
5641            }
5642        }
5643        // Speculative probes: a completed duplicate (",1]") stays rejected, a distinct value
5644        // (",3]") stays accepted, agreeing between the trie walk and the record scan.
5645        let dup_records = [(b",1]".as_slice(), [7u32].as_slice())];
5646        let distinct_records = [(b",3]".as_slice(), [8u32].as_slice())];
5647        let dup_mask = record_matcher
5648            .allowed_mask_from_records(handle.mask_vocab_size(), dup_records.into_iter())
5649            .expect("dup mask");
5650        assert!(!dup_mask.get(TokenId(7)), "duplicate 1 must stay rejected");
5651        let distinct_mask = record_matcher
5652            .allowed_mask_from_records(handle.mask_vocab_size(), distinct_records.into_iter())
5653            .expect("distinct mask");
5654        assert!(
5655            distinct_mask.get(TokenId(8)),
5656            "distinct value 3 must stay accepted"
5657        );
5658        let mut trie_out = [0u8; 4];
5659        trie_matcher
5660            .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
5661            .expect("trie mask after probes");
5662        let trie_bit = |id: u32| trie_out[id as usize / 8] & (1u8 << (id % 8)) != 0;
5663        assert!(!trie_bit(7), "trie: duplicate 1 must stay rejected");
5664        assert!(trie_bit(8), "trie: distinct 3 must stay accepted");
5665        // A properly-terminated distinct value still closes the array cleanly afterward.
5666        assert!(trie_matcher
5667            .advance(b",3]")
5668            .expect("distinct value 3 accepted"));
5669        assert!(trie_matcher.is_accepting());
5670        assert!(record_matcher
5671            .advance(b",3]")
5672            .expect("distinct value 3 accepted"));
5673        assert!(record_matcher.is_accepting());
5674    }
5675
5676    #[test]
5677    fn trie_output_matches_record_scan_and_clears_dirty_output() {
5678        let program = program(
5679            r#"{"type":"object","properties":{"x":{"type":"boolean"}},"required":["x"],"additionalProperties":false}"#,
5680        );
5681        let mut tokens = FxHashMap::default();
5682        tokens.insert(b"{".to_vec(), vec![0]);
5683        tokens.insert(b"\"x\"".to_vec(), vec![1]);
5684        tokens.insert(b":".to_vec(), vec![2]);
5685        tokens.insert(b"true".to_vec(), vec![3, 7]);
5686        tokens.insert(b"false".to_vec(), vec![4]);
5687        tokens.insert(b"}".to_vec(), vec![5]);
5688        let vocabulary = Arc::new(build_vocabulary(8, tokens).expect("vocabulary"));
5689        let handle = VocabularyHandle::new(vocabulary).expect("handle");
5690        let trie = TrieCache::new().bind(&handle).expect("trie");
5691        let mut records = program.new_matcher().expect("records matcher");
5692        let mut walked = program.new_matcher().expect("trie matcher");
5693        for token in [b"{".as_slice(), b"\"x\"", b":"] {
5694            assert!(records.advance(token).expect("advance"));
5695            assert!(walked.advance(token).expect("advance"));
5696        }
5697        let expected = records
5698            .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
5699            .expect("record mask");
5700        let mut output =
5701            vec![0xff; handle.mask_vocab_size().div_ceil(32) * std::mem::size_of::<u32>()];
5702        walked
5703            .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
5704            .expect("trie mask");
5705        for id in 0..handle.mask_vocab_size() {
5706            let token = TokenId(u32::try_from(id).expect("test token id"));
5707            let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
5708            assert_eq!(observed, expected.get(token), "token {id}");
5709        }
5710        assert!(walked.advance(b"true").expect("traversal rolled back"));
5711    }
5712
5713    #[test]
5714    fn trie_record_scan_and_reference_masks_are_bit_identical_at_a_committed_prefix() {
5715        let schema = r#"{"type":"object","additionalProperties":{"type":"boolean"}}"#;
5716        let ir = ir(schema);
5717        let program = StructuredProgram::compile(ir.clone()).expect("program");
5718        assert_eq!(program.backend_kind(), "incremental");
5719        let mut tokens = FxHashMap::default();
5720        let pieces: &[&[u8]] = &[
5721            b"{", b"}", b"\"", b":", b",", b"k", b"0", b"1", b"true", b"false", b"tru", b"e",
5722        ];
5723        for (i, p) in pieces.iter().enumerate() {
5724            tokens.insert(p.to_vec(), vec![i as u32]);
5725        }
5726        tokens.insert(b"}".to_vec(), vec![1, 12]);
5727        let width = 16usize;
5728        let handle = VocabularyHandle::new(Arc::new(build_vocabulary(15, tokens).expect("vocab")))
5729            .expect("handle");
5730        let trie = TrieCache::new().bind(&handle).expect("trie");
5731        let prefix: &[u8] = br#"{"k0":true,"k1":false"#;
5732
5733        let mut walked = program.new_matcher().expect("walked");
5734        let mut scanned = program.new_matcher().expect("scanned");
5735        for &b in prefix {
5736            assert!(walked.advance(&[b]).expect("advance"));
5737            assert!(scanned.advance(&[b]).expect("advance"));
5738        }
5739        let mut trie_out = vec![0u8; width.div_ceil(32) * 4];
5740        walked
5741            .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
5742            .expect("trie mask");
5743        let record_mask = scanned
5744            .allowed_mask_from_records(width, handle.iter_records())
5745            .expect("record mask");
5746
5747        let mut id_bytes: std::collections::HashMap<usize, Vec<u8>> =
5748            std::collections::HashMap::new();
5749        for (i, p) in pieces.iter().enumerate() {
5750            id_bytes.insert(i, p.to_vec());
5751        }
5752        id_bytes.insert(12, b"}".to_vec());
5753        let mut trie_sum = 0u64;
5754        let mut record_sum = 0u64;
5755        let mut ref_sum = 0u64;
5756        for id in 0..width {
5757            let token = TokenId(u32::try_from(id).expect("id"));
5758            let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
5759            let record_bit = record_mask.get(token);
5760            let ref_bit = match id_bytes.get(&id) {
5761                Some(piece) => {
5762                    let mut cand = prefix.to_vec();
5763                    cand.extend_from_slice(piece);
5764                    super::super::reference::accepts(&ir, &cand)
5765                        || super::super::reference::can_continue(&ir, &cand)
5766                }
5767                None => false,
5768            };
5769            assert_eq!(trie_bit, record_bit, "trie vs record at id {id}");
5770            assert_eq!(trie_bit, ref_bit, "trie vs reference at id {id}");
5771            trie_sum |= u64::from(trie_bit) << id;
5772            record_sum |= u64::from(record_bit) << id;
5773            ref_sum |= u64::from(ref_bit) << id;
5774        }
5775        assert_eq!(trie_sum, record_sum, "checksum trie==record");
5776        assert_eq!(trie_sum, ref_sum, "checksum trie==reference");
5777    }
5778
5779    #[test]
5780    fn adaptive_full_trie_and_record_scan_match_on_string_boundaries() {
5781        let schemas_and_prefixes: &[(&str, &[&[u8]])] = &[
5782            (
5783                r#"{"anyOf":[{"type":"object","properties":{"s":{"type":"string","maxLength":1}}},{"type":"object","properties":{"s":{"type":"string","maxLength":3}}}]}"#,
5784                &[br#"{"s":"a"#, br#"{"s":"aa"#],
5785            ),
5786            (
5787                r#"{"type":"object","unevaluatedProperties":{"type":"string","maxLength":1}}"#,
5788                &[br#"{"s":"a"#],
5789            ),
5790            (
5791                r#"{"type":"string","maxLength":40}"#,
5792                &[b"\"", b"\"abcdefgh"],
5793            ),
5794            (
5795                r#"{"type":"string","maxLength":2}"#,
5796                &[b"\"", b"\"a", b"\"aa"],
5797            ),
5798            (
5799                r#"{"type":"object","properties":{"s":{"type":"string","maxLength":2}}}"#,
5800                &[br#"{"s":""#, br#"{"s":"a"#, br#"{"s":"aa"#],
5801            ),
5802            (
5803                r#"{"type":"object","properties":{"s":{"type":"string","maxLength":2}},"unevaluatedProperties":false}"#,
5804                &[br#"{"s":""#, br#"{"s":"a"#, br#"{"s":"aa"#],
5805            ),
5806            (
5807                r#"{"allOf":[{"type":"object"},{"propertyNames":{"maxLength":2}}]}"#,
5808                &[br#"{""#, br#"{"a"#, br#"{"aa"#],
5809            ),
5810            (r#"{"type":"string"}"#, &[b"\"", b"\"a", br#""\u00"#]),
5811            (
5812                r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","pattern":"z$"}]}"#,
5813                &[b"\"a"],
5814            ),
5815            (
5816                r#"{"type":"string","minLength":1,"maxLength":2,"pattern":"^a"}"#,
5817                &[b"\"", b"\"a"],
5818            ),
5819            (
5820                r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","format":"email"}]}"#,
5821                &[b"\"a"],
5822            ),
5823        ];
5824        let mut tokens = FxHashMap::default();
5825        tokens.insert(b"a".to_vec(), vec![0, 1]);
5826        tokens.insert(br#"\u0061"#.to_vec(), vec![2]);
5827        tokens.insert(br#"\uD83D"#.to_vec(), vec![9]);
5828        tokens.insert(br#"\uDE00"#.to_vec(), vec![10]);
5829        tokens.insert(vec![0xf0, 0x9f], vec![3]);
5830        tokens.insert(vec![0x98, 0x80], vec![4]);
5831        tokens.insert(b"\xf0\x9f\x98\x80".to_vec(), vec![5]);
5832        tokens.insert(b"\"".to_vec(), vec![6]);
5833        tokens.insert(b"\\".to_vec(), vec![7]);
5834        tokens.insert(vec![0x1f], vec![8]);
5835        tokens.insert(b"z".to_vec(), vec![100]);
5836        tokens.insert(b"zz".to_vec(), vec![101]);
5837        tokens.insert("\u{e9}".as_bytes().to_vec(), vec![102]);
5838        tokens.insert("\u{20ac}".as_bytes().to_vec(), vec![103]);
5839        tokens.insert("e\u{301}".as_bytes().to_vec(), vec![104]);
5840        tokens.insert(vec![b'a'; 40], vec![105]);
5841        tokens.insert(br#"\uD83D\uDE00"#.to_vec(), vec![106]);
5842        tokens.insert(vec![b'a'; 33], vec![107]);
5843        tokens.insert(vec![b'a'; 65], vec![108]);
5844        tokens.insert(vec![b'a'; 129], vec![109]);
5845        let vocabulary =
5846            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).expect("vocabulary")))
5847                .expect("handle");
5848        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5849        let mask_bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5850
5851        for &(schema, prefixes) in schemas_and_prefixes {
5852            let program = program(schema);
5853            for &prefix in prefixes {
5854                let mut adaptive = program.new_matcher().expect("adaptive matcher");
5855                let mut full = program.new_matcher().expect("full matcher");
5856                let mut records = program.new_matcher().expect("record matcher");
5857                assert!(adaptive.advance(prefix).expect("adaptive prefix"));
5858                assert!(full.advance(prefix).expect("full prefix"));
5859                assert!(records.advance(prefix).expect("record prefix"));
5860                let mut adaptive_out = vec![0u8; mask_bytes];
5861                let mut full_out = vec![0u8; mask_bytes];
5862                let mut record_out = vec![0u8; mask_bytes];
5863                adaptive
5864                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
5865                    .expect("adaptive mask");
5866                #[cfg(not(feature = "bench-internals"))]
5867                let MatcherBackend::Incremental(incremental) = &mut full.backend;
5868                #[cfg(feature = "bench-internals")]
5869                let incremental = match &mut full.backend {
5870                    MatcherBackend::Incremental(incremental) => incremental,
5871                    MatcherBackend::Reference(_) => panic!("expected incremental matcher"),
5872                };
5873                walk_full_trie_mask_for_test(
5874                    incremental,
5875                    &vocabulary,
5876                    &trie,
5877                    &mut full_out,
5878                    program.limits,
5879                )
5880                .expect("full trie mask");
5881                records
5882                    .write_record_mask_le_bytes_into(
5883                        vocabulary.mask_vocab_size(),
5884                        vocabulary.iter_records(),
5885                        &mut record_out,
5886                    )
5887                    .expect("record mask");
5888                assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, "adaptive/full");
5889                assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, "adaptive/record");
5890            }
5891        }
5892    }
5893
5894    #[test]
5895    fn slice_proof_cache_is_exact_bounded_and_evicts() {
5896        let mut cache = SliceProofCache::default();
5897        let bytes = 2 * std::mem::size_of::<SliceProofEntry>();
5898        let key = |tag, state| SliceProofKey {
5899            vocabulary: VocabFingerprint::from_bytes([tag; 32]),
5900            node: NodeId(3),
5901            state: StateId(state),
5902            body_prefix: false,
5903            reject_invalid: false,
5904        };
5905        cache.insert(key(1, 4), true, bytes);
5906        cache.insert(key(2, 4), false, bytes);
5907        assert_eq!(cache.get(key(1, 4)), Some(true));
5908        assert_eq!(cache.get(key(2, 4)), Some(false));
5909        assert_eq!(cache.get(key(1, 5)), None);
5910        assert_eq!(
5911            cache.get(SliceProofKey {
5912                body_prefix: true,
5913                ..key(1, 4)
5914            }),
5915            None
5916        );
5917        cache.insert(key(3, 4), true, bytes);
5918        assert_eq!(cache.entries.len(), 2);
5919        assert_eq!(cache.get(key(1, 4)), None);
5920        let mut disabled = SliceProofCache::default();
5921        disabled.insert(key(1, 4), true, 0);
5922        assert!(disabled.entries.is_empty());
5923    }
5924
5925    #[cfg(feature = "bench-internals")]
5926    #[test]
5927    fn slice_proof_cache_measurements_track_hits_misses_and_entries() {
5928        let mut cache = SliceProofCache::default();
5929        let key = SliceProofKey {
5930            vocabulary: VocabFingerprint::from_bytes([1; 32]),
5931            node: NodeId(3),
5932            state: StateId(4),
5933            body_prefix: false,
5934            reject_invalid: false,
5935        };
5936        cache.insert(key, true, std::mem::size_of::<SliceProofEntry>());
5937        cache.reset_measurements();
5938        assert_eq!(cache.get(key), Some(true));
5939        assert_eq!(
5940            cache.get(SliceProofKey {
5941                state: StateId(5),
5942                ..key
5943            }),
5944            None
5945        );
5946        assert_eq!(
5947            cache.measurements(),
5948            SliceProofMetrics {
5949                hits: 1,
5950                misses: 1,
5951                entries: 1,
5952            }
5953        );
5954    }
5955
5956    #[cfg(feature = "bench-internals")]
5957    #[test]
5958    fn near_limit_scalar_escape_boundary_matrix_matches_all_routes_and_advances() {
5959        struct TokenCase {
5960            name: &'static str,
5961            bytes: Vec<u8>,
5962            decoded_key_bytes: Option<usize>,
5963            string_content_bytes: usize,
5964        }
5965
5966        let cases = vec![
5967            TokenCase {
5968                name: "ascii",
5969                bytes: b"a".to_vec(),
5970                decoded_key_bytes: Some(1),
5971                string_content_bytes: 1,
5972            },
5973            TokenCase {
5974                name: "utf8-2",
5975                bytes: "¢".as_bytes().to_vec(),
5976                decoded_key_bytes: Some(2),
5977                string_content_bytes: 2,
5978            },
5979            TokenCase {
5980                name: "utf8-3",
5981                bytes: "€".as_bytes().to_vec(),
5982                decoded_key_bytes: Some(3),
5983                string_content_bytes: 3,
5984            },
5985            TokenCase {
5986                name: "utf8-4",
5987                bytes: "😀".as_bytes().to_vec(),
5988                decoded_key_bytes: Some(4),
5989                string_content_bytes: 4,
5990            },
5991            TokenCase {
5992                name: "combining-mark",
5993                bytes: "\u{301}".as_bytes().to_vec(),
5994                decoded_key_bytes: Some(2),
5995                string_content_bytes: 2,
5996            },
5997            TokenCase {
5998                name: "json-escape",
5999                bytes: br#"\n"#.to_vec(),
6000                decoded_key_bytes: Some(1),
6001                string_content_bytes: 2,
6002            },
6003            TokenCase {
6004                name: "surrogate-pair",
6005                bytes: br#"\uD83D\uDE00"#.to_vec(),
6006                decoded_key_bytes: Some(4),
6007                string_content_bytes: 12,
6008            },
6009            TokenCase {
6010                name: "partial-utf8",
6011                bytes: vec![0xf0, 0x9f],
6012                decoded_key_bytes: None,
6013                string_content_bytes: 2,
6014            },
6015            TokenCase {
6016                name: "cross-quote",
6017                bytes: b"a\"".to_vec(),
6018                decoded_key_bytes: Some(1),
6019                string_content_bytes: 1,
6020            },
6021            TokenCase {
6022                name: "cross-quote-colon",
6023                bytes: br#"a":"#.to_vec(),
6024                decoded_key_bytes: Some(1),
6025                string_content_bytes: 1,
6026            },
6027            TokenCase {
6028                name: "cross-quote-colon-value",
6029                bytes: br#"a":0"#.to_vec(),
6030                decoded_key_bytes: Some(1),
6031                string_content_bytes: 1,
6032            },
6033        ];
6034        let key_program = program(r#"{"type":"object","additionalProperties":true}"#);
6035        let any_string_program = program(r#"{"type":"object","unevaluatedProperties":true}"#);
6036        let canonical_program =
6037            program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6038        let dependent_program = program(
6039            r#"{"type":"object","dependentSchemas":{"pa":{"type":"object"}},"additionalProperties":true}"#,
6040        );
6041        let property_names_program = program(
6042            r#"{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":true}"#,
6043        );
6044        let key_prefix = br#"{"p"#;
6045        let string_prefix = br#"{"s":"p"#;
6046        let canonical_prefix = br#"["p"#;
6047
6048        for case in cases {
6049            let vocabulary = VocabularyHandle::new(Arc::new(
6050                build_vocabulary(7, FxHashMap::from_iter([(case.bytes.clone(), vec![0])])).unwrap(),
6051            ))
6052            .unwrap();
6053            let trie = TrieCache::new().bind(&vocabulary).unwrap();
6054
6055            let document_exact = key_prefix.len() + case.bytes.len();
6056            for limit in around(document_exact) {
6057                assert_near_limit_three_routes(
6058                    &key_program,
6059                    &vocabulary,
6060                    &trie,
6061                    key_prefix,
6062                    NearLimitTuning::Document(limit),
6063                    &format!("max_document_bytes/{}/{limit}", case.name),
6064                );
6065            }
6066
6067            if let Some(decoded) = case.decoded_key_bytes {
6068                let key_exact = 1 + decoded;
6069                for limit in around(key_exact) {
6070                    assert_near_limit_three_routes(
6071                        &key_program,
6072                        &vocabulary,
6073                        &trie,
6074                        key_prefix,
6075                        NearLimitTuning::Key(limit),
6076                        &format!("max_key_bytes/{}/{limit}", case.name),
6077                    );
6078                }
6079            }
6080
6081            let string_exact = 1 + case.string_content_bytes;
6082            for limit in around(string_exact) {
6083                assert_near_limit_three_routes(
6084                    &any_string_program,
6085                    &vocabulary,
6086                    &trie,
6087                    string_prefix,
6088                    NearLimitTuning::String(limit),
6089                    &format!("max_string_bytes/{}/{limit}", case.name),
6090                );
6091            }
6092
6093            let undo_exact = minimum_successful_limit(
6094                &key_program,
6095                &vocabulary,
6096                &trie,
6097                key_prefix,
6098                StructuredLimits::default().max_undo_entries,
6099                NearLimitTuning::Undo,
6100            );
6101            for limit in around(undo_exact) {
6102                assert_near_limit_three_routes(
6103                    &key_program,
6104                    &vocabulary,
6105                    &trie,
6106                    key_prefix,
6107                    NearLimitTuning::Undo(limit),
6108                    &format!("max_undo_entries/{}/{limit}", case.name),
6109                );
6110            }
6111
6112            let session_exact = minimum_successful_limit(
6113                &key_program,
6114                &vocabulary,
6115                &trie,
6116                key_prefix,
6117                StructuredLimits::default().max_session_bytes,
6118                NearLimitTuning::Session,
6119            );
6120            for limit in around(session_exact) {
6121                assert_near_limit_three_routes(
6122                    &key_program,
6123                    &vocabulary,
6124                    &trie,
6125                    key_prefix,
6126                    NearLimitTuning::Session(limit),
6127                    &format!("max_session_bytes/{}/{limit}", case.name),
6128                );
6129            }
6130
6131            let canonical_exact = minimum_successful_limit(
6132                &canonical_program,
6133                &vocabulary,
6134                &trie,
6135                canonical_prefix,
6136                StructuredLimits::default().max_unique_canonical_bytes,
6137                NearLimitTuning::Canonical,
6138            );
6139            for limit in around(canonical_exact) {
6140                assert_near_limit_three_routes(
6141                    &canonical_program,
6142                    &vocabulary,
6143                    &trie,
6144                    canonical_prefix,
6145                    NearLimitTuning::Canonical(limit),
6146                    &format!("canonical_uniqueItems_storage/{}/{limit}", case.name),
6147                );
6148            }
6149
6150            for (storage_name, storage_program) in [
6151                ("dependent_observer_storage", &dependent_program),
6152                ("propertyNames_cursor_storage", &property_names_program),
6153            ] {
6154                let exact = minimum_successful_limit(
6155                    storage_program,
6156                    &vocabulary,
6157                    &trie,
6158                    key_prefix,
6159                    StructuredLimits::default().max_session_bytes,
6160                    NearLimitTuning::Session,
6161                );
6162                for limit in around(exact) {
6163                    assert_near_limit_three_routes(
6164                        storage_program,
6165                        &vocabulary,
6166                        &trie,
6167                        key_prefix,
6168                        NearLimitTuning::Session(limit),
6169                        &format!("{storage_name}/{}/{limit}", case.name),
6170                    );
6171                }
6172            }
6173        }
6174    }
6175
6176    #[cfg(feature = "bench-internals")]
6177    #[test]
6178    fn active_unique_items_canonical_builder_forces_exact_fallback() {
6179        let program = program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6180        let vocabulary = VocabularyHandle::new(Arc::new(
6181            build_vocabulary(3, FxHashMap::from_iter([(b"abc".to_vec(), vec![0])])).unwrap(),
6182        ))
6183        .unwrap();
6184        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6185        let mut matcher = program.new_matcher().unwrap();
6186        assert!(matcher.advance(br#"["p"#).unwrap());
6187        {
6188            let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
6189                panic!("incremental matcher")
6190            };
6191            let (selected, _) = select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6192            assert!(
6193                selected.is_none(),
6194                "an active canonical builder must conservatively use the exact walker"
6195            );
6196        }
6197        let mut adaptive = [0; 4];
6198        let mut full = [0; 4];
6199        let mut records = [0; 4];
6200        matcher
6201            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
6202            .unwrap();
6203        matcher
6204            .write_full_trie_mask_for_bench(&vocabulary, &trie, &mut full)
6205            .unwrap();
6206        matcher
6207            .write_record_mask_le_bytes_into(
6208                vocabulary.mask_vocab_size(),
6209                vocabulary.iter_records(),
6210                &mut records,
6211            )
6212            .unwrap();
6213        assert_eq!(adaptive, full);
6214        assert_eq!(adaptive, records);
6215        assert_incremental_accounting(&matcher, "canonical exact fallback");
6216    }
6217
6218    #[test]
6219    fn sliced_key_resource_errors_match_record_scan_and_clear_output() {
6220        let schemas = [
6221            r#"{"type":"object","additionalProperties":true}"#,
6222            r#"{"type":"object","unevaluatedProperties":true}"#,
6223            r#"{"allOf":[{"type":"object"},{"additionalProperties":true}]}"#,
6224            r#"{"type":"array","uniqueItems":true,"items":{"type":"object"}}"#,
6225            r#"{"type":"array","contains":{"type":"object"},"items":{"type":"object"}}"#,
6226        ];
6227        for schema in schemas {
6228            let prefix: &[u8] = if schema.contains("array") {
6229                br#"[{"a"#
6230            } else {
6231                br#"{"a"#
6232            };
6233            for budget in [2, 3, 4] {
6234                for document_limit in [false, true] {
6235                    let mut limits = StructuredLimits::default();
6236                    if document_limit {
6237                        limits.max_document_bytes = prefix.len() + budget - 1;
6238                    } else {
6239                        limits.max_key_bytes = budget;
6240                    }
6241                    let program =
6242                        StructuredProgram::compile_with_limits(ir(schema), limits).unwrap();
6243                    let mut tokens = FxHashMap::default();
6244                    tokens.insert(b"abc".to_vec(), vec![0, 1]);
6245                    let vocabulary =
6246                        VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap()))
6247                            .unwrap();
6248                    let trie = TrieCache::new().bind(&vocabulary).unwrap();
6249                    let mut fast = program.new_matcher().unwrap();
6250                    let mut scan = program.new_matcher().unwrap();
6251                    assert!(fast.advance(prefix).unwrap());
6252                    assert!(scan.advance(prefix).unwrap());
6253                    let mut fast_out = [0xa5; 4];
6254                    let mut scan_out = [0xa5; 4];
6255                    let observed =
6256                        fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6257                    let expected = scan.write_record_mask_le_bytes_into(
6258                        vocabulary.mask_vocab_size(),
6259                        vocabulary.iter_records(),
6260                        &mut scan_out,
6261                    );
6262                    assert_eq!(
6263                        observed.is_ok(),
6264                        expected.is_ok(),
6265                        "{schema}, budget={budget}, document={document_limit}"
6266                    );
6267                    assert_eq!(fast_out, scan_out);
6268                    if observed.is_err() {
6269                        assert_eq!(fast_out, [0; 4]);
6270                    } else if fast_out[0] & 1 != 0 {
6271                        assert!(fast.advance(b"abc").unwrap());
6272                    }
6273                }
6274            }
6275        }
6276    }
6277
6278    #[test]
6279    fn wrapped_any_string_byte_limits_match_record_errors_and_advance() {
6280        for limit in [3, 4, 5] {
6281            let limits = StructuredLimits {
6282                max_string_bytes: limit,
6283                ..StructuredLimits::default()
6284            };
6285            let program = StructuredProgram::compile_with_limits(
6286                ir(r#"{"type":"object","unevaluatedProperties":true}"#),
6287                limits,
6288            )
6289            .unwrap();
6290            let mut tokens = FxHashMap::default();
6291            tokens.insert(b"abc".to_vec(), vec![0, 1]);
6292            let vocabulary =
6293                VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6294            let trie = TrieCache::new().bind(&vocabulary).unwrap();
6295            let mut fast = program.new_matcher().unwrap();
6296            let mut scan = program.new_matcher().unwrap();
6297            assert!(fast.advance(br#"{"s":"a"#).unwrap());
6298            assert!(scan.advance(br#"{"s":"a"#).unwrap());
6299            let mut fast_out = [0xa5; 4];
6300            let mut scan_out = [0xa5; 4];
6301            let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6302            let expected = scan.write_record_mask_le_bytes_into(
6303                vocabulary.mask_vocab_size(),
6304                vocabulary.iter_records(),
6305                &mut scan_out,
6306            );
6307            assert_eq!(observed.is_ok(), expected.is_ok());
6308            assert_eq!(observed.is_ok(), limit >= 4);
6309            assert_eq!(fast_out, scan_out);
6310            if observed.is_ok() {
6311                assert!(fast_out[0] & 1 != 0);
6312                assert!(fast.advance(b"abc").unwrap());
6313            } else {
6314                assert_eq!(fast_out, [0; 4]);
6315                assert!(!fast.is_dead());
6316            }
6317        }
6318    }
6319
6320    #[test]
6321    fn unique_item_canonical_limits_match_record_masks_and_advance() {
6322        let mut tokens = FxHashMap::default();
6323        tokens.insert(b"abc".to_vec(), vec![0, 1]);
6324        let vocabulary =
6325            VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6326        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6327        let mut observed_success = false;
6328        let mut observed_limit = false;
6329        for canonical_limit in [16, 32, 64, 128, 1024] {
6330            let program =
6331                program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6332            let mut fast = program.new_matcher().unwrap();
6333            let mut scan = program.new_matcher().unwrap();
6334            assert!(fast.advance(b"[\"").unwrap());
6335            assert!(scan.advance(b"[\"").unwrap());
6336            match &mut fast.backend {
6337                MatcherBackend::Incremental(inner) => {
6338                    inner
6339                        .state
6340                        .set_unique_canonical_limit_for_test(canonical_limit);
6341                }
6342                #[cfg(feature = "bench-internals")]
6343                MatcherBackend::Reference(_) => panic!("incremental matcher"),
6344            }
6345            match &mut scan.backend {
6346                MatcherBackend::Incremental(inner) => {
6347                    inner
6348                        .state
6349                        .set_unique_canonical_limit_for_test(canonical_limit);
6350                }
6351                #[cfg(feature = "bench-internals")]
6352                MatcherBackend::Reference(_) => panic!("incremental matcher"),
6353            }
6354            let mut fast_out = [0xa5; 4];
6355            let mut scan_out = [0xa5; 4];
6356            let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6357            let expected = scan.write_record_mask_le_bytes_into(
6358                vocabulary.mask_vocab_size(),
6359                vocabulary.iter_records(),
6360                &mut scan_out,
6361            );
6362            assert_eq!(
6363                observed.is_ok(),
6364                expected.is_ok(),
6365                "limit={canonical_limit}"
6366            );
6367            assert_eq!(fast_out, scan_out, "limit={canonical_limit}");
6368            if observed.is_ok() && fast_out[0] & 1 != 0 {
6369                observed_success = true;
6370                assert!(fast.advance(b"abc").unwrap());
6371            }
6372            if observed.is_err() {
6373                observed_limit = true;
6374                assert_eq!(fast_out, [0; 4]);
6375                assert!(!fast.is_dead());
6376            }
6377        }
6378        assert!(observed_success);
6379        assert!(observed_limit);
6380    }
6381
6382    #[test]
6383    fn session_limits_match_record_masks_and_advance() {
6384        let mut tokens = FxHashMap::default();
6385        tokens.insert(b"abcdefghijklmnopqrstuvwxyz0123456789".to_vec(), vec![0, 1]);
6386        let vocabulary =
6387            VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6388        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6389        let program = program(r#"{"type":"object","additionalProperties":true}"#);
6390        let mut observed_success = false;
6391        let mut observed_limit = false;
6392        for additional in [0, 1, 32, 65_536] {
6393            let mut fast = program.new_matcher().unwrap();
6394            let mut scan = program.new_matcher().unwrap();
6395            assert!(fast.advance(b"{\"").unwrap());
6396            assert!(scan.advance(b"{\"").unwrap());
6397            let limit = fast
6398                .retained_session_bytes()
6399                .checked_add(additional)
6400                .unwrap();
6401            match &mut fast.backend {
6402                MatcherBackend::Incremental(inner) => {
6403                    inner.state.set_session_limit_for_test(limit);
6404                }
6405                #[cfg(feature = "bench-internals")]
6406                MatcherBackend::Reference(_) => panic!("incremental matcher"),
6407            }
6408            match &mut scan.backend {
6409                MatcherBackend::Incremental(inner) => {
6410                    inner.state.set_session_limit_for_test(limit);
6411                }
6412                #[cfg(feature = "bench-internals")]
6413                MatcherBackend::Reference(_) => panic!("incremental matcher"),
6414            }
6415            let mut fast_out = [0xa5; 4];
6416            let mut scan_out = [0xa5; 4];
6417            let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6418            let expected = scan.write_record_mask_le_bytes_into(
6419                vocabulary.mask_vocab_size(),
6420                vocabulary.iter_records(),
6421                &mut scan_out,
6422            );
6423            assert_eq!(
6424                observed.is_ok(),
6425                expected.is_ok(),
6426                "additional={additional}"
6427            );
6428            assert_eq!(fast_out, scan_out, "additional={additional}");
6429            if observed.is_ok() && fast_out[0] & 1 != 0 {
6430                observed_success = true;
6431                assert!(fast
6432                    .advance(b"abcdefghijklmnopqrstuvwxyz0123456789")
6433                    .unwrap());
6434            }
6435            if observed.is_err() {
6436                observed_limit = true;
6437                assert_eq!(fast_out, [0; 4]);
6438                assert!(!fast.is_dead());
6439            }
6440        }
6441        assert!(observed_success);
6442        assert!(observed_limit);
6443    }
6444
6445    #[test]
6446    #[cfg(feature = "bench-internals")]
6447    fn bounded_string_slice_fires_and_warm_retention_stabilizes() {
6448        let program =
6449            program(r#"{"type":"object","properties":{"s":{"type":"string","maxLength":3}}}"#);
6450        let tokens = (0..100u32)
6451            .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6452            .chain([
6453                (b"a".to_vec(), vec![100]),
6454                (b"\"".to_vec(), vec![101]),
6455                ("\u{1f600}".as_bytes().to_vec(), vec![102]),
6456            ])
6457            .collect();
6458        let vocabulary =
6459            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6460        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6461        let mut matcher = program.new_matcher().unwrap();
6462        assert!(matcher.advance(br#"{"s":"a"#).unwrap());
6463        let mut output = [0; 16];
6464        matcher
6465            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6466            .unwrap();
6467        let retained = match &matcher.backend {
6468            MatcherBackend::Incremental(inner) => inner.state.retained_bytes(),
6469            _ => panic!("incremental program"),
6470        };
6471        for _ in 0..8 {
6472            matcher
6473                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6474                .unwrap();
6475            assert!(
6476                matcher.mask_work_metrics_for_bench().trie_nodes
6477                    < trie.trie().node_count() as u64 / 2
6478            );
6479            let MatcherBackend::Incremental(inner) = &matcher.backend else {
6480                panic!("incremental program")
6481            };
6482            assert_eq!(inner.state.retained_bytes(), retained);
6483        }
6484    }
6485
6486    #[test]
6487    #[cfg(feature = "bench-internals")]
6488    fn wrapper_slices_fire_and_match_both_exact_walks_after_warmup() {
6489        let cases = [
6490            (
6491                r#"{"type":"object","properties":{"mode":{"type":"string"},"text":{"type":"string"}},"dependentSchemas":{"mode":{"required":["text"],"properties":{"text":{"maxLength":12}}}}}"#,
6492                br#"{"mo"#.as_slice(),
6493            ),
6494            (
6495                r#"{"type":"object","properties":{"mode":{"type":"string"},"text":{"type":"string"}},"dependentSchemas":{"mode":{"required":["text"],"properties":{"text":{"maxLength":12}}}}}"#,
6496                br#"{"mode":"text","text":"rea"#.as_slice(),
6497            ),
6498            (
6499                r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6500                br#"{"owner":"ops","team":""#.as_slice(),
6501            ),
6502            (
6503                r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6504                br#"{"owner":"ops","team":"core"#.as_slice(),
6505            ),
6506            (
6507                r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6508                br#"{"owner":"o"#.as_slice(),
6509            ),
6510            (
6511                r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6512                br#"{"owner":"1234567"#.as_slice(),
6513            ),
6514            (
6515                r#"{"type":"object","unevaluatedProperties":true}"#,
6516                br#"{"a"#.as_slice(),
6517            ),
6518            (
6519                r#"{"allOf":[{"type":"object"},{"additionalProperties":true}]}"#,
6520                br#"{"a"#,
6521            ),
6522            (
6523                r##"{"$dynamicAnchor":"node","type":"object","properties":{"child":{"$dynamicRef":"#node"}},"additionalProperties":true}"##,
6524                br#"{"child":{"a"#,
6525            ),
6526            (
6527                r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
6528                br#"["rea"#,
6529            ),
6530            (
6531                r#"{"type":"array","items":{"type":"string"},"contains":{"const":"ok"},"minContains":2,"maxItems":2}"#,
6532                br#"["o"#,
6533            ),
6534            (
6535                r#"{"type":"array","contains":{"type":"string"},"unevaluatedItems":false}"#,
6536                br#"["rea"#,
6537            ),
6538            (
6539                r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"unevaluatedItems":false}"#,
6540                br#"["#,
6541            ),
6542            (
6543                r#"{"type":"array","prefixItems":[{"type":"string","maxLength":12}],"contains":{"type":"string","pattern":"^ok"},"unevaluatedItems":false}"#,
6544                br#"["#,
6545            ),
6546        ];
6547        let tokens = (0..100u32)
6548            .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6549            .chain([
6550                (b"\"".to_vec(), vec![100]),
6551                (br#"\u0061"#.to_vec(), vec![101]),
6552                (vec![0xf0, 0x9f], vec![102]),
6553            ])
6554            .collect();
6555        let vocabulary =
6556            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6557        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6558        for (schema, prefix) in cases {
6559            let program = program(schema);
6560            let mut matcher = program.new_matcher().unwrap();
6561            assert!(matcher.advance(prefix).unwrap());
6562            let mut fast = [0; 16];
6563            let mut full = [0; 16];
6564            let mut scan = [0; 16];
6565            matcher
6566                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
6567                .unwrap();
6568            assert!(
6569                matcher.mask_work_metrics_for_bench().trie_nodes
6570                    < trie.trie().node_count() as u64 / 2,
6571                "cold {schema}"
6572            );
6573            let cold = fast;
6574            matcher
6575                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
6576                .unwrap();
6577            assert!(
6578                matcher.mask_work_metrics_for_bench().trie_nodes
6579                    < trie.trie().node_count() as u64 / 2,
6580                "{schema}"
6581            );
6582            let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
6583                panic!("incremental program")
6584            };
6585            walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
6586                .unwrap();
6587            matcher
6588                .write_record_mask_le_bytes_into(
6589                    vocabulary.mask_vocab_size(),
6590                    vocabulary.iter_records(),
6591                    &mut scan,
6592                )
6593                .unwrap();
6594            assert_eq!(fast, full, "{schema}");
6595            assert_eq!(fast, scan, "{schema}");
6596            assert_eq!(cold, full, "cold {schema}");
6597        }
6598    }
6599
6600    #[test]
6601    #[cfg(feature = "bench-internals")]
6602    fn safe_interior_array_certificate_matrix_matches_all_routes_and_advances() {
6603        #[derive(Clone, Copy)]
6604        struct Case {
6605            name: &'static str,
6606            schema: &'static str,
6607            prefix: &'static [u8],
6608            fast: bool,
6609        }
6610        let cases = [
6611            Case {
6612                name: "empty-array-structural-position",
6613                schema: r#"{"type":"array","items":{"type":"string"}}"#,
6614                prefix: b"[",
6615                fast: false,
6616            },
6617            Case {
6618                name: "prefix-item",
6619                schema: r#"{"prefixItems":[{"type":"string"}],"items":false}"#,
6620                prefix: br#"["pre"#,
6621                fast: true,
6622            },
6623            Case {
6624                name: "prefix-to-tail",
6625                schema: r#"{"prefixItems":[{"type":"string"}],"items":{"type":"string"}}"#,
6626                prefix: br#"["a","ta"#,
6627                fast: true,
6628            },
6629            Case {
6630                name: "nested-array-item",
6631                schema: r#"{"type":"array","items":{"type":"array","items":{"type":"string"}}}"#,
6632                prefix: br#"[["ne"#,
6633                fast: true,
6634            },
6635            Case {
6636                name: "contains-observer",
6637                schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"minContains":1}"#,
6638                prefix: br#"["ok"#,
6639                fast: true,
6640            },
6641            Case {
6642                name: "unevaluated-items-observer",
6643                schema: r#"{"prefixItems":[true],"contains":{"type":"string"},"unevaluatedItems":false}"#,
6644                prefix: br#"[1,"ok"#,
6645                fast: true,
6646            },
6647            Case {
6648                name: "maximum-items-interior",
6649                schema: r#"{"type":"array","items":{"type":"string"},"maxItems":2}"#,
6650                prefix: br#"["a"#,
6651                fast: true,
6652            },
6653            Case {
6654                name: "canonical-unique-items-fallback",
6655                schema: r#"{"type":"array","items":{"type":"string"},"uniqueItems":true}"#,
6656                prefix: br#"["a"#,
6657                fast: false,
6658            },
6659        ];
6660        let mut tokens = (0..100u32)
6661            .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6662            .collect::<FxHashMap<_, _>>();
6663        for (id, bytes) in [
6664            (100, b"\"".as_slice()),
6665            (101, b"\","),
6666            (102, b"\"]"),
6667            (103, b","),
6668            (104, b"]"),
6669            (105, br#"\u0061"#),
6670            (106, b"\xf0\x9f"),
6671        ] {
6672            tokens.insert(bytes.to_vec(), vec![id]);
6673        }
6674        let vocabulary =
6675            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6676        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6677        let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
6678
6679        for case in cases {
6680            let program = program(case.schema);
6681            let mut adaptive = program.new_matcher().unwrap();
6682            let mut full = program.new_matcher().unwrap();
6683            let mut record = program.new_matcher().unwrap();
6684            for matcher in [&mut adaptive, &mut full, &mut record] {
6685                assert!(matcher.advance(case.prefix).unwrap(), "{}", case.name);
6686            }
6687            {
6688                let MatcherBackend::Incremental(inner) = &mut adaptive.backend else {
6689                    panic!("incremental matcher")
6690                };
6691                let (selected, _) =
6692                    select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6693                assert_eq!(selected.is_some(), case.fast, "{}", case.name);
6694            }
6695            let mut adaptive_out = vec![0; output_len];
6696            let mut full_out = vec![0; output_len];
6697            let mut record_out = vec![0; output_len];
6698            adaptive
6699                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6700                .unwrap();
6701            if case.fast {
6702                assert!(
6703                    adaptive.mask_work_metrics_for_bench().trie_nodes
6704                        < trie.trie().node_count() as u64,
6705                    "{} fast path did not fire",
6706                    case.name
6707                );
6708            }
6709            {
6710                let MatcherBackend::Incremental(inner) = &mut full.backend else {
6711                    panic!("incremental matcher")
6712                };
6713                select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6714                walk_full_trie_mask_for_test(
6715                    inner,
6716                    &vocabulary,
6717                    &trie,
6718                    &mut full_out,
6719                    program.limits,
6720                )
6721                .unwrap();
6722            }
6723            {
6724                let MatcherBackend::Incremental(inner) = &mut record.backend else {
6725                    panic!("incremental matcher")
6726                };
6727                select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6728            }
6729            record
6730                .write_record_mask_le_bytes_into(
6731                    vocabulary.mask_vocab_size(),
6732                    vocabulary.iter_records(),
6733                    &mut record_out,
6734                )
6735                .unwrap();
6736            assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, case.name);
6737            assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, case.name);
6738
6739            for (candidate, ids) in vocabulary.iter_records() {
6740                if !ids
6741                    .iter()
6742                    .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6743                {
6744                    continue;
6745                }
6746                let mut equivalent = program.new_matcher().unwrap();
6747                assert!(equivalent.advance(case.prefix).unwrap(), "{}", case.name);
6748                let mut prepared = vec![0; output_len];
6749                equivalent
6750                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6751                    .unwrap();
6752                assert_eq!(prepared, adaptive_out, "{}", case.name);
6753                assert!(
6754                    equivalent.advance(candidate).unwrap(),
6755                    "{} emitted {ids:?}",
6756                    case.name
6757                );
6758                assert_incremental_accounting(&equivalent, case.name);
6759            }
6760            for matcher in [&adaptive, &full, &record] {
6761                assert_incremental_accounting(matcher, case.name);
6762            }
6763        }
6764
6765        let tail = cases
6766            .iter()
6767            .find(|case| case.name == "prefix-to-tail")
6768            .unwrap();
6769        let program = program(tail.schema);
6770        let mut matcher = program.new_matcher().unwrap();
6771        assert!(matcher.advance(tail.prefix).unwrap());
6772        let mut output = vec![0; output_len];
6773        matcher
6774            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6775            .unwrap();
6776        assert_ne!(output[101 / 8] & (1 << (101 % 8)), 0);
6777    }
6778
6779    #[test]
6780    #[cfg(feature = "bench-internals")]
6781    fn combined_unevaluated_certificate_matches_all_routes_and_advances() {
6782        let schema = r#"{
6783            "type":"object",
6784            "properties":{
6785                "mode":{"enum":["fast","slow"]},
6786                "count":{"type":"integer","minimum":0,"maximum":9},
6787                "code":{"type":"string","pattern":"^[A-Z]{2}[0-9]{2}$"},
6788                "items":{"type":"array","items":{"type":"string"},"contains":{"const":"required"},"uniqueItems":true,"minItems":1,"maxItems":3},
6789                "meta":{"type":"object","propertyNames":{"pattern":"^[a-z]+$"},"patternProperties":{"^[a-z]+$":{"type":"string"}},"additionalProperties":false}
6790            },
6791            "required":["mode","count","code","items","meta"],
6792            "dependentRequired":{"mode":["code"]},
6793            "if":{"properties":{"mode":{"const":"fast"}},"required":["mode"]},
6794            "then":{"properties":{"count":{"maximum":5}}},
6795            "else":{"properties":{"count":{"minimum":6}}},
6796            "allOf":[
6797                {"if":{"properties":{"code":{"pattern":"^OK"}},"required":["code"]},"then":{"properties":{"meta":{"minProperties":1}}},"else":{"properties":{"meta":{"maxProperties":2}}}},
6798                {"if":{"properties":{"count":{"maximum":5}},"required":["count"]},"then":{"properties":{"items":{"maxItems":3}}},"else":{"properties":{"items":{"minItems":2}}}}
6799            ],
6800            "unevaluatedProperties":false
6801        }"#;
6802        let tokens = (0..100u32)
6803            .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6804            .chain([(b"\"".to_vec(), vec![100])])
6805            .collect();
6806        let vocabulary =
6807            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6808        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6809        let program = program(schema);
6810        let prefix = br#"{"mo"#;
6811        let mut adaptive = program.new_matcher().unwrap();
6812        let mut full = program.new_matcher().unwrap();
6813        let mut record = program.new_matcher().unwrap();
6814        for matcher in [&mut adaptive, &mut full, &mut record] {
6815            assert!(matcher.advance(prefix).unwrap());
6816        }
6817        let mut adaptive_out = [0; 16];
6818        let mut full_out = [0; 16];
6819        let mut record_out = [0; 16];
6820        adaptive
6821            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6822            .unwrap();
6823        assert!(
6824            adaptive.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64 / 2,
6825            "combined unevaluated certificate did not activate"
6826        );
6827        {
6828            let MatcherBackend::Incremental(inner) = &mut full.backend else {
6829                panic!("incremental matcher")
6830            };
6831            select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6832            walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full_out, program.limits)
6833                .unwrap();
6834        }
6835        {
6836            let MatcherBackend::Incremental(inner) = &mut record.backend else {
6837                panic!("incremental matcher")
6838            };
6839            select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6840        }
6841        record
6842            .write_record_mask_le_bytes_into(
6843                vocabulary.mask_vocab_size(),
6844                vocabulary.iter_records(),
6845                &mut record_out,
6846            )
6847            .unwrap();
6848        assert_mask_bytes_equal(
6849            &adaptive_out,
6850            &full_out,
6851            &vocabulary,
6852            "combined unevaluated",
6853        );
6854        assert_mask_bytes_equal(
6855            &adaptive_out,
6856            &record_out,
6857            &vocabulary,
6858            "combined unevaluated",
6859        );
6860
6861        let first = adaptive_out;
6862        adaptive
6863            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6864            .unwrap();
6865        assert_eq!(adaptive_out, first, "speculative rollback changed the mask");
6866        for (candidate, ids) in vocabulary.iter_records() {
6867            if !ids
6868                .iter()
6869                .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6870            {
6871                continue;
6872            }
6873            let mut equivalent = program.new_matcher().unwrap();
6874            assert!(equivalent.advance(prefix).unwrap());
6875            let mut prepared = [0; 16];
6876            equivalent
6877                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6878                .unwrap();
6879            assert_eq!(prepared, adaptive_out);
6880            assert!(equivalent.advance(candidate).unwrap(), "emitted {ids:?}");
6881            assert_incremental_accounting(&equivalent, "combined unevaluated advance");
6882        }
6883        for matcher in [&adaptive, &full, &record] {
6884            assert_incremental_accounting(matcher, "combined unevaluated route");
6885        }
6886    }
6887
6888    #[cfg(feature = "bench-internals")]
6889    #[test]
6890    fn lexical_prefix_walk_matches_exact_routes_and_every_emitted_token_advances() {
6891        let tokens = FxHashMap::from_iter([
6892            (b"abc\"".to_vec(), vec![0]),
6893            (b"abc\"}".to_vec(), vec![1]),
6894            (b"ab\\u0063\"}".to_vec(), vec![2]),
6895            ("é\"}".as_bytes().to_vec(), vec![3]),
6896            (vec![0xf0, 0x9f], vec![4]),
6897            (vec![0xf0, 0x9f, b'"'], vec![5]),
6898            (b"ab\n\"".to_vec(), vec![6]),
6899            (b"\\\"still".to_vec(), vec![7]),
6900            (b"\"}".to_vec(), vec![8]),
6901        ]);
6902        let vocabulary =
6903            VocabularyHandle::new(Arc::new(build_vocabulary(31, tokens).unwrap())).unwrap();
6904        let trie = TrieCache::new().bind(&vocabulary).unwrap();
6905        let program = program(
6906            r#"{"type":"object","properties":{"note":{"type":"string"}},"required":["note"]}"#,
6907        );
6908        let prefix = br#"{"note":""#;
6909        let mut adaptive = program.new_matcher().unwrap();
6910        let mut full = program.new_matcher().unwrap();
6911        let mut record = program.new_matcher().unwrap();
6912        for matcher in [&mut adaptive, &mut full, &mut record] {
6913            assert!(matcher.advance(prefix).unwrap());
6914        }
6915        if let MatcherBackend::Incremental(inner) = &mut adaptive.backend {
6916            let mark = inner.state.checkpoint();
6917            for &byte in b"abc\"" {
6918                assert!(inner.state.try_push_byte_for_mask(byte).unwrap());
6919            }
6920            assert!(inner.state.next_byte_is_lexically_impossible(b'a'));
6921            assert!(!inner.state.next_byte_is_lexically_impossible(b'}'));
6922            inner.state.rollback(mark);
6923        }
6924        let mut adaptive_out = [0u8; 4];
6925        let mut full_out = [0u8; 4];
6926        let mut record_out = [0u8; 4];
6927        adaptive
6928            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6929            .unwrap();
6930        let metrics = adaptive.mask_work_metrics_for_bench();
6931        assert!(metrics.local_dfa_transitions > 0);
6932        let (event_capacity, path_capacity) = match &adaptive.backend {
6933            MatcherBackend::Incremental(inner) => {
6934                (inner.events.capacity(), inner.lexical_path.capacity())
6935            }
6936            MatcherBackend::Reference(_) => panic!("incremental matcher"),
6937        };
6938        let MatcherBackend::Incremental(inner) = &mut full.backend else {
6939            panic!("incremental matcher")
6940        };
6941        walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full_out, program.limits)
6942            .unwrap();
6943        record
6944            .write_record_mask_le_bytes_into(
6945                vocabulary.mask_vocab_size(),
6946                vocabulary.iter_records(),
6947                &mut record_out,
6948            )
6949            .unwrap();
6950        assert_eq!(adaptive_out, full_out);
6951        assert_eq!(adaptive_out, record_out);
6952
6953        adaptive
6954            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6955            .unwrap();
6956        let MatcherBackend::Incremental(inner) = &adaptive.backend else {
6957            panic!("incremental matcher")
6958        };
6959        assert_eq!(inner.events.capacity(), event_capacity);
6960        assert_eq!(inner.lexical_path.capacity(), path_capacity);
6961
6962        for (bytes, ids) in vocabulary.iter_records() {
6963            if !ids
6964                .iter()
6965                .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6966            {
6967                continue;
6968            }
6969            let mut equivalent = program.new_matcher().unwrap();
6970            assert!(equivalent.advance(prefix).unwrap());
6971            let mut prepared = [0u8; 4];
6972            equivalent
6973                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6974                .unwrap();
6975            assert_eq!(prepared, adaptive_out);
6976            assert!(equivalent.advance(bytes).unwrap(), "emitted token {ids:?}");
6977        }
6978    }
6979
6980    #[cfg(feature = "bench-internals")]
6981    #[test]
6982    fn contains_product_uncertain_states_use_the_exact_walker() {
6983        let cases = [
6984            (
6985                "fresh-item",
6986                r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
6987                b"[".as_slice(),
6988            ),
6989            (
6990                "at-maximum",
6991                r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":1}"#,
6992                br#"["ok","a"#,
6993            ),
6994            (
6995                "multiple-structured-candidates",
6996                r#"{"type":"array","maxItems":1,"items":{"type":"array","maxItems":1,"items":{"type":"string"},"contains":{"type":"string","pattern":"^a"},"minContains":1},"contains":{"type":"array","contains":{"type":"string","pattern":"^a"},"minContains":1},"minContains":1}"#,
6997                br#"[["a"#,
6998            ),
6999        ];
7000        let vocabulary = VocabularyHandle::new(Arc::new(
7001            build_vocabulary(
7002                8,
7003                FxHashMap::from_iter([
7004                    (b"word".to_vec(), vec![0]),
7005                    (b"x".to_vec(), vec![1]),
7006                    (b"\"".to_vec(), vec![2]),
7007                ]),
7008            )
7009            .unwrap(),
7010        ))
7011        .unwrap();
7012        let trie = TrieCache::new().bind(&vocabulary).unwrap();
7013        for (name, schema, prefix) in cases {
7014            let program = program(schema);
7015            let mut matcher = program.new_matcher().unwrap();
7016            assert!(matcher.advance(prefix).unwrap(), "{name}");
7017            {
7018                let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7019                    panic!("incremental matcher")
7020                };
7021                let (selected, _) =
7022                    select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
7023                assert!(selected.is_none(), "{name} must remain exact");
7024            }
7025            let mut adaptive = [0; 4];
7026            let mut full = [0; 4];
7027            let mut record = [0; 4];
7028            matcher
7029                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
7030                .unwrap();
7031            matcher
7032                .write_full_trie_mask_for_bench(&vocabulary, &trie, &mut full)
7033                .unwrap();
7034            matcher
7035                .write_record_mask_le_bytes_into(
7036                    vocabulary.mask_vocab_size(),
7037                    vocabulary.iter_records(),
7038                    &mut record,
7039                )
7040                .unwrap();
7041            assert_eq!(adaptive, full, "{name}");
7042            assert_eq!(adaptive, record, "{name}");
7043            assert_incremental_accounting(&matcher, name);
7044        }
7045    }
7046
7047    #[cfg(feature = "bench-internals")]
7048    #[test]
7049    fn invalid_body_prefixes_skip_the_parser_only_with_a_cold_rejection_proof() {
7050        let tokens = (0..100u32)
7051            .map(|id| (format!("prefix{id}\n").into_bytes(), vec![id]))
7052            .chain([
7053                (b"a".to_vec(), vec![100]),
7054                (b"a\"\n".to_vec(), vec![101]),
7055                (b"\xf0\x9f".to_vec(), vec![102]),
7056            ])
7057            .collect();
7058        let vocabulary =
7059            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
7060        let trie = TrieCache::new().bind(&vocabulary).unwrap();
7061        for (schema, prefix) in [
7062            (
7063                r#"{"type":"object","unevaluatedProperties":true}"#,
7064                br#"{"a"#.as_slice(),
7065            ),
7066            (
7067                r#"{"type":"object","properties":{"s":{"type":"string"}}}"#,
7068                br#"{"s":"a"#.as_slice(),
7069            ),
7070        ] {
7071            let program = program(schema);
7072            let mut matcher = program.new_matcher().unwrap();
7073            assert!(matcher.advance(prefix).unwrap());
7074            let mut fast = [0; 16];
7075            let mut full = [0; 16];
7076            let mut scan = [0; 16];
7077            matcher
7078                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
7079                .unwrap();
7080            assert!(
7081                matcher.mask_work_metrics_for_bench().trie_nodes < 10,
7082                "{schema}"
7083            );
7084            assert!(
7085                matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64
7086            );
7087            let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7088                panic!("incremental program")
7089            };
7090            walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
7091                .unwrap();
7092            matcher
7093                .write_record_mask_le_bytes_into(
7094                    vocabulary.mask_vocab_size(),
7095                    vocabulary.iter_records(),
7096                    &mut scan,
7097                )
7098                .unwrap();
7099            assert_eq!(fast, full, "{schema}");
7100            assert_eq!(fast, scan, "{schema}");
7101            assert_ne!(
7102                fast[101 / 8] & (1 << (101 % 8)),
7103                0,
7104                "whitespace after closing quote"
7105            );
7106        }
7107    }
7108
7109    #[test]
7110    fn invalid_body_filter_preserves_errors_before_lexical_rejection() {
7111        let tokens = [(b"abc\n".to_vec(), vec![0])].into_iter().collect();
7112        let vocabulary =
7113            VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
7114        let trie = TrieCache::new().bind(&vocabulary).unwrap();
7115        let prefix = br#"{"a"#;
7116        for remaining in 1..=5 {
7117            let limits = StructuredLimits {
7118                max_document_bytes: prefix.len() + remaining,
7119                ..StructuredLimits::default()
7120            };
7121            let program = StructuredProgram::compile_with_limits(
7122                ir(r#"{"type":"object","unevaluatedProperties":true}"#),
7123                limits,
7124            )
7125            .unwrap();
7126            let mut fast = program.new_matcher().unwrap();
7127            let mut scan = program.new_matcher().unwrap();
7128            assert!(fast.advance(prefix).unwrap());
7129            assert!(scan.advance(prefix).unwrap());
7130            let mut fast_out = [0xff; 4];
7131            let mut scan_out = [0xff; 4];
7132            let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
7133            let expected = scan.write_record_mask_le_bytes_into(
7134                vocabulary.mask_vocab_size(),
7135                vocabulary.iter_records(),
7136                &mut scan_out,
7137            );
7138            assert_eq!(observed.is_ok(), expected.is_ok(), "remaining={remaining}");
7139            assert_eq!(fast_out, scan_out, "remaining={remaining}");
7140            assert_eq!(fast_out, [0; 4]);
7141        }
7142    }
7143
7144    #[cfg(feature = "bench-internals")]
7145    #[test]
7146    fn escaped_and_partial_body_slice_fires_on_a_cold_wrapper() {
7147        let tokens = (0..100u32)
7148            .map(|id| (format!("\\u0061{id}").into_bytes(), vec![id]))
7149            .chain([
7150                (b"\"".to_vec(), vec![100]),
7151                (vec![0xf0, 0x9f], vec![101]),
7152                (b"\"\n".to_vec(), vec![102]),
7153                (b"\xff".to_vec(), vec![103]),
7154                (b"plain".to_vec(), vec![104]),
7155            ])
7156            .collect();
7157        let vocabulary =
7158            VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
7159        let trie = TrieCache::new().bind(&vocabulary).unwrap();
7160        let program = program(r#"{"type":"object","unevaluatedProperties":true}"#);
7161        let mut matcher = program.new_matcher().unwrap();
7162        assert!(matcher.advance(br#"{"a"#).unwrap());
7163        let mut fast = [0; 16];
7164        let mut full = [0; 16];
7165        let mut scan = [0; 16];
7166        matcher
7167            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
7168            .unwrap();
7169        assert!(matcher.mask_work_metrics_for_bench().trie_nodes < 20);
7170        assert!(matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64);
7171        let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7172            panic!("incremental program")
7173        };
7174        walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
7175        matcher
7176            .write_record_mask_le_bytes_into(
7177                vocabulary.mask_vocab_size(),
7178                vocabulary.iter_records(),
7179                &mut scan,
7180            )
7181            .unwrap();
7182        assert_eq!(fast, full);
7183        assert_eq!(fast, scan);
7184    }
7185
7186    #[cfg(feature = "bench-internals")]
7187    #[test]
7188    fn adaptive_slice_removes_at_least_eighty_percent_of_string_edges() {
7189        let program = program(
7190            r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","pattern":"z$"}]}"#,
7191        );
7192        let mut tokens = FxHashMap::default();
7193        for id in 0..512u32 {
7194            tokens.insert(format!("token{id:04}").into_bytes(), vec![id]);
7195        }
7196        tokens.insert(b"\"".to_vec(), vec![600]);
7197        tokens.insert(b"\\".to_vec(), vec![601]);
7198        let vocabulary =
7199            VocabularyHandle::new(Arc::new(build_vocabulary(700, tokens).expect("vocabulary")))
7200                .expect("handle");
7201        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7202        let mut adaptive = program.new_matcher().expect("adaptive");
7203        let mut full = program.new_matcher().expect("full");
7204        assert!(adaptive.advance(b"\"a").expect("prefix"));
7205        assert!(full.advance(b"\"a").expect("prefix"));
7206        let bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
7207        let mut adaptive_out = vec![0u8; bytes];
7208        let mut full_out = vec![0u8; bytes];
7209        adaptive
7210            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
7211            .expect("adaptive warmup");
7212        adaptive_out.fill(0);
7213        adaptive
7214            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
7215            .expect("adaptive mask");
7216        let adaptive_metrics = adaptive.mask_work_metrics_for_bench();
7217        let incremental = match &mut full.backend {
7218            MatcherBackend::Incremental(incremental) => incremental,
7219            MatcherBackend::Reference(_) => panic!("expected incremental matcher"),
7220        };
7221        incremental.reset_mask_metrics();
7222        walk_full_trie_mask_for_test(
7223            incremental,
7224            &vocabulary,
7225            &trie,
7226            &mut full_out,
7227            program.limits,
7228        )
7229        .expect("full mask");
7230        incremental.finish_mask_metrics();
7231        let full_metrics = incremental.mask_work_metrics;
7232        assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, "adaptive/full");
7233        assert!(
7234            adaptive_metrics.trie_edges.saturating_mul(5) <= full_metrics.trie_edges,
7235            "adaptive={} full={}",
7236            adaptive_metrics.trie_edges,
7237            full_metrics.trie_edges
7238        );
7239    }
7240
7241    #[test]
7242    fn concurrent_matchers_share_catalogue_without_sharing_state() {
7243        let program = program(r#"{"type":"string"}"#);
7244        let mut tokens = FxHashMap::default();
7245        tokens.insert(b"a".to_vec(), vec![0]);
7246        tokens.insert(b"b".to_vec(), vec![1]);
7247        tokens.insert(b"\"".to_vec(), vec![2]);
7248        let vocabulary =
7249            VocabularyHandle::new(Arc::new(build_vocabulary(3, tokens).expect("vocabulary")))
7250                .expect("handle");
7251        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7252        let expected = {
7253            let mut matcher = program.new_matcher().expect("expected matcher");
7254            assert!(matcher.advance(b"\"").expect("prefix"));
7255            let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7256            matcher
7257                .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7258                .expect("expected mask");
7259            output
7260        };
7261        let threads: Vec<_> = (0..8)
7262            .map(|_| {
7263                let program = program.clone();
7264                let vocabulary = vocabulary.clone();
7265                let trie = trie.clone();
7266                std::thread::spawn(move || {
7267                    let mut matcher = program.new_matcher().expect("thread matcher");
7268                    assert!(matcher.advance(b"\"").expect("thread prefix"));
7269                    let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7270                    matcher
7271                        .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7272                        .expect("thread mask");
7273                    output
7274                })
7275            })
7276            .collect();
7277        for thread in threads {
7278            assert_eq!(thread.join().expect("thread"), expected);
7279        }
7280    }
7281
7282    fn assert_mask_bytes_equal(
7283        left: &[u8],
7284        right: &[u8],
7285        vocabulary: &VocabularyHandle,
7286        label: &str,
7287    ) {
7288        if left == right {
7289            return;
7290        }
7291        let differences: Vec<_> = (0..vocabulary.mask_vocab_size())
7292            .filter(|id| left[id / 8] & (1 << (id % 8)) != right[id / 8] & (1 << (id % 8)))
7293            .map(|id| (id, vocabulary.token_bytes(u32::try_from(id).unwrap())))
7294            .collect();
7295        panic!("{label} mismatch: {differences:?}");
7296    }
7297
7298    #[test]
7299    fn vocabulary_a_with_trie_b_is_artifact_mismatch_and_leaves_output_zeroed() {
7300        let program = program(r#"{"type":"object","additionalProperties":{"type":"boolean"}}"#);
7301        let mut tokens_a = FxHashMap::default();
7302        tokens_a.insert(b"{".to_vec(), vec![0]);
7303        tokens_a.insert(b"}".to_vec(), vec![1]);
7304        let mut tokens_b = FxHashMap::default();
7305        tokens_b.insert(b"{".to_vec(), vec![0]);
7306        tokens_b.insert(b"\"".to_vec(), vec![1]);
7307        tokens_b.insert(b"}".to_vec(), vec![2]);
7308        let handle_a =
7309            VocabularyHandle::new(Arc::new(build_vocabulary(3, tokens_a).expect("vocab a")))
7310                .expect("handle a");
7311        let handle_b =
7312            VocabularyHandle::new(Arc::new(build_vocabulary(4, tokens_b).expect("vocab b")))
7313                .expect("handle b");
7314        let trie_b = TrieCache::new().bind(&handle_b).expect("trie b");
7315        let mut matcher = program.new_matcher().expect("matcher");
7316        let mut output = vec![0xff; handle_a.mask_vocab_size().div_ceil(32) * 4];
7317        let error = matcher
7318            .write_mask_le_bytes_into(&handle_a, Some(&trie_b), &mut output)
7319            .expect_err("vocab a paired with trie b must mismatch");
7320        assert!(matches!(
7321            error,
7322            StructuredMatcherError::Compile(CompileError {
7323                code: ErrorCode::ArtifactMismatch,
7324                ..
7325            })
7326        ));
7327        assert!(
7328            output.iter().all(|&b| b == 0),
7329            "output must be cleared on mismatch"
7330        );
7331    }
7332
7333    #[test]
7334    fn direct_output_rejects_a_wrong_length_before_mutation() {
7335        let program = program(r#"{"type":"string"}"#);
7336        let vocabulary = Arc::new(build_vocabulary(1, FxHashMap::default()).expect("vocabulary"));
7337        let handle = VocabularyHandle::new(vocabulary).expect("handle");
7338        let trie = TrieCache::new().bind(&handle).expect("trie");
7339        let mut matcher = program.new_matcher().expect("matcher");
7340        let mut output = [0xa5u8; 2];
7341        let error = matcher
7342            .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7343            .expect_err("wrong output length");
7344        assert!(matches!(
7345            error,
7346            StructuredMatcherError::Compile(CompileError {
7347                code: ErrorCode::ArtifactOutOfBounds,
7348                ..
7349            })
7350        ));
7351        assert_eq!(output, [0xa5; 2]);
7352    }
7353
7354    fn assert_combinator_prefixes(schema: &str, document: &[u8], valid: bool) -> usize {
7355        let schema_ir = ir(schema);
7356        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7357        assert!(program.uses_incremental_backend(), "{schema}");
7358        let mut matcher = program.new_matcher().expect("matcher");
7359        let mut prefix = Vec::new();
7360        for &byte in document {
7361            let before_accepting = matcher.is_accepting();
7362            let before_dead = matcher.is_dead();
7363            let mut candidate = prefix.clone();
7364            candidate.push(byte);
7365            let allowed = matcher.advance(&[byte]).expect("incremental byte");
7366            let expected = super::super::reference::accepts(&schema_ir, &candidate)
7367                || super::super::reference::can_continue(&schema_ir, &candidate);
7368            assert_eq!(allowed, expected, "prefix {candidate:?}, schema {schema}");
7369            if !allowed {
7370                assert_eq!(matcher.is_accepting(), before_accepting);
7371                assert_eq!(matcher.is_dead(), before_dead);
7372                let records = [([byte], [0u32])];
7373                let mask = matcher
7374                    .allowed_mask_from_records(
7375                        1,
7376                        records
7377                            .iter()
7378                            .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
7379                    )
7380                    .expect("rejected-byte mask");
7381                assert!(!mask.get(TokenId(0)));
7382                return candidate.len();
7383            }
7384            prefix.push(byte);
7385            assert_eq!(
7386                matcher.is_accepting(),
7387                super::super::reference::accepts(&schema_ir, &prefix),
7388                "acceptance at {prefix:?}, schema {schema}"
7389            );
7390        }
7391        assert_eq!(matcher.is_accepting(), valid, "{document:?}, {schema}");
7392        prefix.len()
7393    }
7394
7395    #[test]
7396    fn non_regular_combinator_hard_correctness_matrix_matches_reference_at_every_prefix() {
7397        type CombinatorCase<'a> = (&'a str, &'a [(&'a [u8], bool)]);
7398        let cases: &[CombinatorCase<'_>] = &[
7399            (
7400                r#"{"allOf":[{"type":"object","properties":{"id":{"type":"integer"}},"required":["id"],"additionalProperties":true},{"type":"object","patternProperties":{"^x":{"type":"string"}},"additionalProperties":true}]}"#,
7401                &[
7402                    (r#"{"xé":"λ","id":1}"#.as_bytes(), true),
7403                    (br#"{"id":1,"x":"\u03bb"}"#, true),
7404                ],
7405            ),
7406            (
7407                r#"{"allOf":[{"type":"array","items":{"type":"integer"},"contains":{"const":2},"minContains":1},{"type":"array","uniqueItems":true,"minItems":2}]}"#,
7408                &[(b"[1,2]", true), (b"[1,3]", false)],
7409            ),
7410            (
7411                r#"{"anyOf":[{"enum":["aaaaaaaaab","aaaaaaaaac"]},{"type":"string","pattern":"^aaaaaaaaad+$"},{"type":"array","items":{"type":"integer"},"uniqueItems":true}]}"#,
7412                &[
7413                    (br#""aaaaaaaaac""#, true),
7414                    (br#""aaaaaaaaadd""#, true),
7415                    (b"[1,2]", true),
7416                    (br#""aaaaaaaaa""#, false),
7417                ],
7418            ),
7419            (
7420                r#"{"oneOf":[{"const":1},{"type":"number"},{"type":"object","additionalProperties":{"type":"integer"}}]}"#,
7421                &[(b"1", false), (b"12", true), (br#"{"x":1}"#, true)],
7422            ),
7423            (
7424                r#"{"oneOf":[{"type":"string","pattern":"^.*$"},{"type":"string","pattern":"^.*$"},{"type":"array","uniqueItems":true}]}"#,
7425                &[(br#""a""#, false), (b"[]", true)],
7426            ),
7427            (
7428                r#"{"allOf":[{"anyOf":[{"oneOf":[{"type":"object","required":["a"],"additionalProperties":true},{"type":"array","uniqueItems":true}]},{"type":"null"}]},{"anyOf":[{"type":"object","additionalProperties":true},{"type":"array","contains":{"const":1}},{"type":"null"}]}]}"#,
7429                &[
7430                    (r#" { "a" : "λ" } "#.as_bytes(), true),
7431                    (b" [ 1 ] ", true),
7432                    (b"null", true),
7433                    (br#"{}"#, false),
7434                ],
7435            ),
7436            (
7437                r##"{"$defs":{"node":{"anyOf":[{"type":"null"},{"type":"object","properties":{"value":{"type":"integer"},"next":{"$ref":"#/$defs/node"}},"required":["value","next"],"additionalProperties":false}]}},"$ref":"#/$defs/node"}"##,
7438                &[
7439                    (b"null", true),
7440                    (br#"{"value":1,"next":{"value":2,"next":null}}"#, true),
7441                    (br#"{"value":1,"next":]"#, false),
7442                ],
7443            ),
7444        ];
7445        for (schema, documents) in cases {
7446            for (document, valid) in *documents {
7447                assert_combinator_prefixes(schema, document, *valid);
7448            }
7449        }
7450    }
7451
7452    #[test]
7453    fn negation_and_conditional_prefixes_match_the_reference() {
7454        type Case<'a> = (&'a str, &'a [(&'a [u8], bool)]);
7455        let cases: &[Case<'_>] = &[
7456            (
7457                r#"{"not":{"const":1}}"#,
7458                &[(b"1", false), (b"12", true), (b"2", true)],
7459            ),
7460            (
7461                r#"{"not":{"enum":[null,true,"x"]}}"#,
7462                &[(b"null", false), (b"false", true), (br#""x""#, false)],
7463            ),
7464            (
7465                r#"{"not":{"type":"object","required":["x"],"additionalProperties":true}}"#,
7466                &[(br#"{"x":1}"#, false), (br#"{"y":1}"#, true), (b"[]", true)],
7467            ),
7468            (
7469                r#"{"not":{"type":"array","uniqueItems":true}}"#,
7470                &[(b"[1,2]", false), (b"[1,1]", true), (b"{}", true)],
7471            ),
7472            (
7473                r#"{"not":{"type":"array","contains":{"const":1}}}"#,
7474                &[(b"[1]", false), (b"[2]", true), (b"{}", true)],
7475            ),
7476            (
7477                r#"{"not":{"type":"number","minimum":-1,"maximum":2}}"#,
7478                &[(b"-0", false), (b"2.0", false), (b"3", true)],
7479            ),
7480            (
7481                r#"{"not":{"type":"number"}}"#,
7482                &[(b"1e2", false), (b"-0e+9", false), (br#""1e2""#, true)],
7483            ),
7484            (
7485                r#"{"not":{"type":"string","pattern":"^λ+$"}}"#,
7486                &[
7487                    (r#""λ""#.as_bytes(), false),
7488                    (br#""\u03bb""#, false),
7489                    (br#""x""#, true),
7490                ],
7491            ),
7492            (
7493                r#"{"not":{"allOf":[{"type":"object"},{"required":["x"]}]}}"#,
7494                &[(br#"{"x":1}"#, false), (b"{}", true), (b"[]", true)],
7495            ),
7496            (
7497                r#"{"not":{"anyOf":[{"type":"null"},{"const":1}]}}"#,
7498                &[(b"null", false), (b"1", false), (b"2", true)],
7499            ),
7500            (
7501                r#"{"not":{"oneOf":[{"const":1},{"type":"number"}]}}"#,
7502                &[(b"1", true), (b"2", false), (b"null", true)],
7503            ),
7504            (
7505                r#"{"not":{"not":{"type":"string"}}}"#,
7506                &[(br#""a""#, true), (b"1", false)],
7507            ),
7508            (
7509                r#"{"if":{"type":"object","required":["kind"],"properties":{"kind":{"const":"a"}},"additionalProperties":true},"then":{"required":["a"]},"else":{"required":["b"]}}"#,
7510                &[
7511                    (br#"{"kind":"a","a":1}"#, true),
7512                    (br#"{"a":1,"kind":"a"}"#, true),
7513                    (br#"{"kind":"b","b":1}"#, true),
7514                    (br#"{"kind":"a","b":1}"#, false),
7515                ],
7516            ),
7517            (
7518                r#"{"if":{"type":"integer"},"then":{"minimum":2}}"#,
7519                &[(b"1", false), (b"2", true), (br#""x""#, true)],
7520            ),
7521            (
7522                r#"{"if":{"type":"integer"},"else":{"type":"string"}}"#,
7523                &[(b"1", true), (br#""x""#, true)],
7524            ),
7525            (
7526                r#"{"if":true,"then":{"type":"integer"},"else":false}"#,
7527                &[(b"1", true)],
7528            ),
7529            (
7530                r#"{"if":false,"then":false,"else":{"type":"string"}}"#,
7531                &[(br#""x""#, true)],
7532            ),
7533            (
7534                r#"{"type":"array","items":{"if":{"type":"integer"},"then":{"minimum":0},"else":{"type":"string"}}}"#,
7535                &[(br#"[0,"x"]"#, true)],
7536            ),
7537            (
7538                r##"{"$defs":{"condition":{"type":"object","required":["kind"],"properties":{"kind":{"const":"a"}},"additionalProperties":true},"branch":{"required":["a"]}},"if":{"$ref":"#/$defs/condition"},"then":{"$ref":"#/$defs/branch"},"else":{"required":["b"]}}"##,
7539                &[
7540                    (br#"{"kind":"a","a":1}"#, true),
7541                    (br#"{"kind":"b","b":1}"#, true),
7542                ],
7543            ),
7544            (
7545                r#"{"if":{"allOf":[{"type":"object"},{"anyOf":[{"required":["x"]},{"required":["y"]}]}]},"then":{"oneOf":[{"required":["a"]},{"required":["b"]}]},"else":false}"#,
7546                &[
7547                    (br#"{"x":1,"a":1}"#, true),
7548                    (br#"{"y":1,"a":1,"b":1}"#, false),
7549                ],
7550            ),
7551        ];
7552        let mut document_total = 0usize;
7553        let mut prefix_total = 0usize;
7554        for (schema, documents) in cases {
7555            for &(document, valid) in *documents {
7556                document_total += 1;
7557                prefix_total += assert_combinator_prefixes(schema, document, valid);
7558            }
7559        }
7560        println!("documents={document_total} byte_prefixes={prefix_total}");
7561    }
7562
7563    #[test]
7564    fn combinator_trie_record_and_reference_masks_are_identical() {
7565        let schema = r#"{"oneOf":[{"const":1},{"type":"number"},{"type":"object","additionalProperties":{"type":"integer"}}]}"#;
7566        let schema_ir = ir(schema);
7567        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7568        assert!(program.uses_incremental_backend());
7569        let mut tokens = FxHashMap::default();
7570        for (id, bytes) in [
7571            (0u32, b"1".as_slice()),
7572            (1, b"12"),
7573            (2, br#"{"x":1}"#),
7574            (3, br#""x""#),
7575        ] {
7576            tokens.insert(bytes.to_vec(), vec![id]);
7577        }
7578        let vocabulary = crate::index::VocabularyHandle::new(Arc::new(
7579            build_vocabulary(4, tokens).expect("vocabulary"),
7580        ))
7581        .expect("handle");
7582        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7583        let mut record_matcher = program.new_matcher().expect("record matcher");
7584        let mut trie_matcher = program.new_matcher().expect("trie matcher");
7585        let record = record_matcher
7586            .allowed_mask_from_records(vocabulary.mask_vocab_size(), vocabulary.iter_records())
7587            .expect("record mask");
7588        let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7589        trie_matcher
7590            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7591            .expect("trie mask");
7592        for id in 0..vocabulary.mask_vocab_size() {
7593            let token = TokenId(u32::try_from(id).unwrap());
7594            let trie_bit = output[id / 8] & (1 << (id % 8)) != 0;
7595            assert_eq!(trie_bit, record.get(token), "trie/record id {id}");
7596            let bytes = vocabulary
7597                .iter_records()
7598                .find(|(_, ids)| ids.contains(&(id as u32)))
7599                .map(|(bytes, _)| bytes);
7600            let oracle = bytes.is_some_and(|bytes| {
7601                super::super::reference::accepts(&schema_ir, bytes)
7602                    || super::super::reference::can_continue(&schema_ir, bytes)
7603            });
7604            assert_eq!(trie_bit, oracle, "reference id {id}");
7605        }
7606    }
7607
7608    #[test]
7609    fn negation_trie_record_and_reference_masks_are_identical() {
7610        let schema = r#"{"not":{"const":1}}"#;
7611        let schema_ir = ir(schema);
7612        let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7613        assert!(program.uses_incremental_backend());
7614        let mut tokens = FxHashMap::default();
7615        for (id, bytes) in [
7616            (0u32, b"1".as_slice()),
7617            (1, b"12"),
7618            (2, b"1 "),
7619            (3, b"2"),
7620            (4, br#""x""#),
7621            (5, b"[1]"),
7622        ] {
7623            tokens.insert(bytes.to_vec(), vec![id]);
7624        }
7625        let vocabulary = crate::index::VocabularyHandle::new(Arc::new(
7626            build_vocabulary(6, tokens).expect("vocabulary"),
7627        ))
7628        .expect("handle");
7629        let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7630        let mut records = program.new_matcher().expect("record matcher");
7631        let mut walked = program.new_matcher().expect("trie matcher");
7632        let record = records
7633            .allowed_mask_from_records(vocabulary.mask_vocab_size(), vocabulary.iter_records())
7634            .expect("record mask");
7635        let mut direct_output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7636        records
7637            .write_record_mask_le_bytes_into(
7638                vocabulary.mask_vocab_size(),
7639                vocabulary.iter_records(),
7640                &mut direct_output,
7641            )
7642            .expect("direct record output");
7643        let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7644        walked
7645            .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7646            .expect("trie mask");
7647        for id in 0..vocabulary.mask_vocab_size() {
7648            let token = TokenId(u32::try_from(id).unwrap());
7649            let trie_bit = output[id / 8] & (1 << (id % 8)) != 0;
7650            let direct_bit = direct_output[id / 8] & (1 << (id % 8)) != 0;
7651            assert_eq!(direct_bit, record.get(token), "direct/record id {id}");
7652            assert_eq!(trie_bit, record.get(token), "trie/record id {id}");
7653            let bytes = vocabulary
7654                .iter_records()
7655                .find(|(_, ids)| ids.contains(&u32::try_from(id).unwrap()))
7656                .map(|(bytes, _)| bytes);
7657            let oracle = bytes.is_some_and(|bytes| {
7658                super::super::reference::accepts(&schema_ir, bytes)
7659                    || super::super::reference::can_continue(&schema_ir, bytes)
7660            });
7661            assert_eq!(trie_bit, oracle, "reference id {id}");
7662        }
7663    }
7664
7665    #[test]
7666    fn trie_scratch_limit_rejects_without_changing_matcher_state() {
7667        let limits = StructuredLimits {
7668            max_mask_scratch_bytes: 0,
7669            ..StructuredLimits::default()
7670        };
7671        let program = StructuredProgram::compile_with_limits(ir(r#"{"type":"string"}"#), limits)
7672            .expect("program");
7673        let mut tokens = FxHashMap::default();
7674        tokens.insert(b"\"".to_vec(), vec![0]);
7675        let handle =
7676            VocabularyHandle::new(Arc::new(build_vocabulary(1, tokens).expect("vocabulary")))
7677                .expect("handle");
7678        let trie = TrieCache::new().bind(&handle).expect("trie");
7679        let mut matcher = program.new_matcher().expect("matcher");
7680        let mut output = [0xa5; 4];
7681        let error = matcher
7682            .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7683            .expect_err("scratch limit");
7684        assert!(matches!(
7685            error,
7686            StructuredMatcherError::ResourceLimit {
7687                kind: LimitKind::MaskScratchBytes,
7688                ..
7689            }
7690        ));
7691        assert_eq!(output, [0; 4]);
7692        assert!(!matcher.is_dead());
7693    }
7694
7695    #[test]
7696    fn trie_scratch_capacity_stabilizes_after_warm_masks() {
7697        let program = program(r#"{"type":"string"}"#);
7698        let mut tokens = FxHashMap::default();
7699        tokens.insert(b"\"a".to_vec(), vec![0]);
7700        tokens.insert(b"\"b".to_vec(), vec![1]);
7701        let handle =
7702            VocabularyHandle::new(Arc::new(build_vocabulary(2, tokens).expect("vocabulary")))
7703                .expect("handle");
7704        let trie = TrieCache::new().bind(&handle).expect("trie");
7705        let mut matcher = program.new_matcher().expect("matcher");
7706        let mut output = [0; 4];
7707        matcher
7708            .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7709            .expect("warmup");
7710        let capacity = match &matcher.backend {
7711            MatcherBackend::Incremental(incremental) => incremental.events.capacity(),
7712            #[cfg(feature = "bench-internals")]
7713            MatcherBackend::Reference(_) => panic!("production program selected reference backend"),
7714        };
7715        for _ in 0..32 {
7716            matcher
7717                .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7718                .expect("warm mask");
7719        }
7720        let after = match &matcher.backend {
7721            MatcherBackend::Incremental(incremental) => incremental.events.capacity(),
7722            #[cfg(feature = "bench-internals")]
7723            MatcherBackend::Reference(_) => panic!("production program selected reference backend"),
7724        };
7725        assert_eq!(after, capacity);
7726    }
7727
7728    /// Exercises full object generation with an overlapping byte-level vocabulary.
7729    /// Direct trie masks must match record scans at every commit step.
7730    #[test]
7731    fn byte_level_vocabulary_trie_mask_matches_record_scan_across_a_full_generation() {
7732        let program = program(
7733            r#"{"type":"object","properties":{"id":{"type":"integer"},"ok":{"type":"boolean"}},"required":["id","ok"],"additionalProperties":false}"#,
7734        );
7735        let mut tokens = FxHashMap::default();
7736        let pieces: &[&[u8]] = &[
7737            b"{", b"}", b"\"", b":", b",", b" ", b"id", b"ok", b"i", b"d", b"o", b"k", b"\"id\"",
7738            b"\"ok\"", b"true", b"false", b"tru", b"e", b"0", b"1", b"42", b"-",
7739        ];
7740        for (i, piece) in pieces.iter().enumerate() {
7741            tokens.insert(piece.to_vec(), vec![i as u32]);
7742        }
7743        // Duplicate byte string mapped to two ids: both must be set together by the trie walk.
7744        tokens.insert(b"}".to_vec(), vec![1, 40]);
7745        let width = 64usize;
7746        let handle =
7747            VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7748                .expect("handle");
7749        let trie = TrieCache::new().bind(&handle).expect("trie");
7750
7751        let doc: &[&[u8]] = &[
7752            b"{", b"\"id\"", b":", b"42", b",", b"\"ok\"", b":", b"true", b"}",
7753        ];
7754        let mut records = program.new_matcher().expect("records");
7755        let mut walked = program.new_matcher().expect("walked");
7756        let byte_len = width.div_ceil(32) * std::mem::size_of::<u32>();
7757        for step in doc {
7758            let expected = records
7759                .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7760                .expect("record mask");
7761            let mut output = vec![0xff; byte_len];
7762            walked
7763                .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7764                .expect("trie mask");
7765            for id in 0..handle.mask_vocab_size() {
7766                let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
7767                let token = TokenId(u32::try_from(id).expect("token id"));
7768                assert_eq!(observed, expected.get(token), "id {id} at step {step:?}");
7769            }
7770            assert!(records.advance(step).expect("records advance"), "{step:?}");
7771            assert!(walked.advance(step).expect("walked advance"), "{step:?}");
7772        }
7773        assert!(walked.is_accepting());
7774        assert!(records.is_accepting());
7775    }
7776
7777    /// A token may end in an accepting nested regular frame.
7778    /// The frame remains active until a later byte closes it.
7779    #[test]
7780    fn nested_string_token_ending_accepting_keeps_the_frame_and_matches_the_record_scan() {
7781        let program = program(
7782            r#"{"type":"object","properties":{"n":{"type":"string"}},"required":["n"],"additionalProperties":false}"#,
7783        );
7784        let mut tokens = FxHashMap::default();
7785        // `ab` and `abc` both end accepting inside the string body; the quote is a separate token,
7786        // so the frame must survive every one of them.
7787        let pieces: &[&[u8]] = &[
7788            b"{", b"}", b"\"", b":", b",", b"n", b"\"n\"", b"ab", b"abc", b"c", b"z", b" ",
7789        ];
7790        for (i, piece) in pieces.iter().enumerate() {
7791            tokens.insert(piece.to_vec(), vec![i as u32]);
7792        }
7793        let width = 64usize;
7794        let handle =
7795            VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7796                .expect("handle");
7797        let trie = TrieCache::new().bind(&handle).expect("trie");
7798
7799        let doc: &[&[u8]] = &[
7800            b"{", b"\"n\"", b":", b"\"", b"ab", b"abc", b"c", b"\"", b"}",
7801        ];
7802        let mut records = program.new_matcher().expect("records");
7803        let mut walked = program.new_matcher().expect("walked");
7804        let byte_len = width.div_ceil(32) * std::mem::size_of::<u32>();
7805        for step in doc {
7806            let expected = records
7807                .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7808                .expect("record mask");
7809            let mut output = vec![0xff; byte_len];
7810            walked
7811                .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7812                .expect("trie mask");
7813            for id in 0..handle.mask_vocab_size() {
7814                let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
7815                let token = TokenId(u32::try_from(id).expect("token id"));
7816                assert_eq!(observed, expected.get(token), "id {id} at step {step:?}");
7817            }
7818            assert!(records.advance(step).expect("records advance"), "{step:?}");
7819            assert!(walked.advance(step).expect("walked advance"), "{step:?}");
7820        }
7821        assert!(walked.is_accepting());
7822        assert!(records.is_accepting());
7823    }
7824
7825    /// The nested slice path must actually be taken, or the test above proves only that two slow
7826    /// paths agree. Inside a nested string body the walk must stay off the full trie.
7827    #[test]
7828    #[cfg(feature = "bench-internals")]
7829    fn nested_string_body_mask_walks_only_the_uncertain_trie() {
7830        let program = program(
7831            r#"{"type":"object","properties":{"n":{"type":"string"}},"required":["n"],"additionalProperties":false}"#,
7832        );
7833        let mut tokens = FxHashMap::default();
7834        let pieces: &[&[u8]] = &[
7835            b"{", b"}", b"\"", b":", b"n", b"\"n\"", b"ab", b"cd", b"ef", b"gh", b"ij", b"kl",
7836        ];
7837        for (i, piece) in pieces.iter().enumerate() {
7838            tokens.insert(piece.to_vec(), vec![i as u32]);
7839        }
7840        let handle =
7841            VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7842                .expect("handle");
7843        let trie = TrieCache::new().bind(&handle).expect("trie");
7844        let mut matcher = program.new_matcher().expect("matcher");
7845        let mut output = vec![0u8; 64usize.div_ceil(32) * std::mem::size_of::<u32>()];
7846
7847        for step in [b"{".as_slice(), b"\"n\"", b":", b"\""] {
7848            matcher
7849                .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7850                .expect("mask");
7851            assert!(matcher.advance(step).expect("advance"), "{step:?}");
7852        }
7853        matcher
7854            .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7855            .expect("string body mask");
7856        let nodes = matcher.mask_work_metrics_for_bench().trie_nodes;
7857        let full = trie.trie().node_count() as u64;
7858        assert!(
7859            nodes < full,
7860            "string body walked {nodes} of {full} nodes; the slice never fired"
7861        );
7862    }
7863
7864    #[test]
7865    fn trie_record_and_reference_masks_agree_after_every_uniqueitems_prefix() {
7866        let schema = r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true,"contains":{"const":0}}"#;
7867        let doc_ir = ir(schema);
7868        let program = StructuredProgram::compile(doc_ir.clone()).expect("program");
7869        assert_eq!(program.backend_kind(), "incremental");
7870        let mut tokens = FxHashMap::default();
7871        for (id, bytes) in [
7872            (0u32, b"[".as_slice()),
7873            (1, b"0"),
7874            (2, b","),
7875            (3, b"1"),
7876            (4, b"2"),
7877            (5, b"]"),
7878        ] {
7879            tokens.insert(bytes.to_vec(), vec![id]);
7880        }
7881        let handle = VocabularyHandle::new(Arc::new(build_vocabulary(6, tokens).expect("vocab")))
7882            .expect("handle");
7883        let trie = TrieCache::new().bind(&handle).expect("trie");
7884        let mut walked = program.new_matcher().expect("trie matcher");
7885        let mut recorded = program.new_matcher().expect("record matcher");
7886        let prefixes: &[&[u8]] = &[b"[", b"[0", b"[0,1", b"[0,1,2"];
7887        let steps: &[&[u8]] = &[b"[", b"0", b",1", b",2"];
7888        let mut committed = Vec::new();
7889        for (prefix, step) in prefixes.iter().zip(steps.iter()) {
7890            for &b in *step {
7891                assert!(walked.advance(&[b]).expect("advance"));
7892                assert!(recorded.advance(&[b]).expect("advance"));
7893            }
7894            committed.extend_from_slice(step);
7895            assert_eq!(committed.as_slice(), *prefix);
7896
7897            let record_mask = recorded
7898                .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7899                .expect("record mask");
7900            let mut trie_out = vec![0u8; handle.mask_vocab_size().div_ceil(32) * 4];
7901            walked
7902                .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
7903                .expect("trie mask");
7904            for id in 0..handle.mask_vocab_size() {
7905                let token = TokenId(u32::try_from(id).expect("id"));
7906                let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
7907                let record_bit = record_mask.get(token);
7908                assert_eq!(trie_bit, record_bit, "prefix {prefix:?} id {id}");
7909                let id32 = u32::try_from(id).expect("id");
7910                let Some((bytes, _)) = handle.iter_records().find(|(_, ids)| ids.contains(&id32))
7911                else {
7912                    continue;
7913                };
7914                let mut candidate = prefix.to_vec();
7915                candidate.extend_from_slice(bytes);
7916                let ref_bit = super::super::reference::accepts(&doc_ir, &candidate)
7917                    || super::super::reference::can_continue(&doc_ir, &candidate);
7918                assert_eq!(trie_bit, ref_bit, "prefix {prefix:?} id {id} vs reference");
7919            }
7920        }
7921    }
7922
7923    #[test]
7924    fn unevaluated_prefix_masks_match_record_preallocated_trie_and_reference() {
7925        let cases = [
7926            (
7927                r#"{"anyOf":[{"properties":{"a":true}},{"properties":{"b":true}}],"unevaluatedProperties":false}"#,
7928                br#"{"a":1,"b":2}"#.as_slice(),
7929            ),
7930            (
7931                r#"{"prefixItems":[true],"contains":{"type":"string"},"unevaluatedItems":false}"#,
7932                br#"[1,"foo"]"#.as_slice(),
7933            ),
7934            (
7935                r#"{"properties":{"v":true},"unevaluatedProperties":true}"#,
7936                br#"{"v":[{"deep":[1,2]}]}"#.as_slice(),
7937            ),
7938        ];
7939        let mut token_map = FxHashMap::default();
7940        for byte in 0u8..=127 {
7941            token_map.insert(vec![byte], vec![u32::from(byte)]);
7942        }
7943        let handle = VocabularyHandle::new(Arc::new(
7944            build_vocabulary(128, token_map).expect("ASCII vocabulary"),
7945        ))
7946        .expect("handle");
7947        let trie = TrieCache::new().bind(&handle).expect("trie");
7948        let output_bytes = handle.mask_vocab_size().div_ceil(32) * 4;
7949        let mut bytes_by_id = vec![None; handle.mask_vocab_size()];
7950        for (bytes, ids) in handle.iter_records() {
7951            for &id in ids {
7952                bytes_by_id[usize::try_from(id).unwrap()] = Some(bytes.to_vec());
7953            }
7954        }
7955
7956        for (schema, document) in cases {
7957            let schema_ir = ir(schema);
7958            let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7959            let mut record = program.new_matcher().expect("record matcher");
7960            let mut preallocated = program.new_matcher().expect("preallocated matcher");
7961            let mut walked = program.new_matcher().expect("trie matcher");
7962            assert_eq!(record.backend_name(), "incremental", "{schema}");
7963            assert_eq!(preallocated.backend_name(), "incremental", "{schema}");
7964            assert_eq!(walked.backend_name(), "incremental", "{schema}");
7965            let mut whitespace = program.new_matcher().unwrap();
7966            assert!(
7967                whitespace.advance(b"\t").unwrap(),
7968                "leading whitespace {schema}"
7969            );
7970            let direct = record
7971                .allowed_mask_from_records(
7972                    128,
7973                    std::iter::once((b"\t".as_slice(), [9u32].as_slice())),
7974                )
7975                .unwrap();
7976            assert!(direct.get(TokenId(9)), "direct tab mask {schema}");
7977
7978            for prefix_len in 0..=document.len() {
7979                let prefix = &document[..prefix_len];
7980                let record_mask = record
7981                    .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7982                    .expect("record mask");
7983                let mut preallocated_out = vec![0xff; output_bytes];
7984                preallocated
7985                    .write_record_mask_le_bytes_into(
7986                        handle.mask_vocab_size(),
7987                        handle.iter_records(),
7988                        &mut preallocated_out,
7989                    )
7990                    .expect("preallocated record mask");
7991                let mut trie_out = vec![0xff; output_bytes];
7992                walked
7993                    .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
7994                    .expect("trie mask");
7995
7996                for id in 0..handle.mask_vocab_size() {
7997                    let token = TokenId(u32::try_from(id).unwrap());
7998                    let record_bit = record_mask.get(token);
7999                    let preallocated_bit = preallocated_out[id / 8] & (1u8 << (id % 8)) != 0;
8000                    let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
8001                    assert_eq!(
8002                        record_bit, preallocated_bit,
8003                        "{schema} prefix={prefix:?} id={id}"
8004                    );
8005                    assert_eq!(record_bit, trie_bit, "{schema} prefix={prefix:?} id={id}");
8006                    if let Some(bytes) = &bytes_by_id[id] {
8007                        let mut candidate = prefix.to_vec();
8008                        candidate.extend_from_slice(bytes);
8009                        let reference_bit =
8010                            super::super::reference::accepts(&schema_ir, &candidate)
8011                                || super::super::reference::can_continue(&schema_ir, &candidate);
8012                        assert_eq!(
8013                            record_bit, reference_bit,
8014                            "{schema} prefix={prefix:?} id={id} bytes={bytes:?}"
8015                        );
8016                    }
8017                }
8018                assert_eq!(
8019                    record.eos_legal(),
8020                    super::super::reference::accepts(&schema_ir, prefix)
8021                );
8022                if prefix_len < document.len() {
8023                    let byte = &document[prefix_len..prefix_len + 1];
8024                    assert!(record.advance(byte).unwrap(), "{schema} prefix={prefix:?}");
8025                    assert!(preallocated.advance(byte).unwrap());
8026                    assert!(walked.advance(byte).unwrap());
8027                }
8028            }
8029        }
8030    }
8031
8032    #[cfg(feature = "bench-internals")]
8033    #[test]
8034    fn finite_unevaluated_key_prefix_rejects_dead_end_and_matches_all_routes() {
8035        let schema = r#"{"type":"object","required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#;
8036        let structured = program(schema);
8037        assert_eq!(structured.backend_kind(), "incremental");
8038        let mut tokens = FxHashMap::default();
8039        for byte in 0u8..=255 {
8040            tokens.insert(vec![byte], vec![u32::from(byte)]);
8041        }
8042        for (id, bytes) in [
8043            (256, b"method".as_slice()),
8044            (257, b"bogus-dead-branch-one"),
8045            (258, b"bogus-dead-branch-two"),
8046        ] {
8047            tokens.insert(bytes.to_vec(), vec![id]);
8048        }
8049        let handle =
8050            VocabularyHandle::new(Arc::new(build_vocabulary(259, tokens).expect("vocabulary")))
8051                .expect("handle");
8052        let trie = TrieCache::new().bind(&handle).expect("trie");
8053        let output_len = handle.mask_vocab_size().div_ceil(32) * 4;
8054
8055        for prefix in [br#"{""#.as_slice(), br#"{"method""#, br#"{"method":"card""#] {
8056            let mut adaptive = structured.new_matcher().expect("adaptive");
8057            let mut full = structured.new_matcher().expect("full");
8058            let mut record = structured.new_matcher().expect("record");
8059            for matcher in [&mut adaptive, &mut full, &mut record] {
8060                assert!(matcher.advance(prefix).expect("valid prefix"), "{prefix:?}");
8061            }
8062            let mut adaptive_out = vec![0xa5; output_len];
8063            let mut full_out = vec![0xa5; output_len];
8064            let mut record_out = vec![0xa5; output_len];
8065            adaptive
8066                .write_mask_le_bytes_into(&handle, Some(&trie), &mut adaptive_out)
8067                .expect("adaptive mask");
8068            let adaptive_nodes = adaptive.mask_work_metrics_for_bench().trie_nodes;
8069            full.write_full_trie_mask_for_bench(&handle, &trie, &mut full_out)
8070                .expect("full mask");
8071            record
8072                .write_record_mask_le_bytes_into(
8073                    handle.mask_vocab_size(),
8074                    handle.iter_records(),
8075                    &mut record_out,
8076                )
8077                .expect("record mask");
8078            assert_eq!(adaptive_out, full_out, "adaptive/full at {prefix:?}");
8079            assert_eq!(adaptive_out, record_out, "adaptive/record at {prefix:?}");
8080            if prefix == br#"{""# {
8081                assert!(
8082                    adaptive_nodes < trie.trie().node_count() as u64,
8083                    "finite key pruning did not activate: {adaptive_nodes} nodes"
8084                );
8085            }
8086
8087            for (bytes, ids) in handle.iter_records() {
8088                if !ids
8089                    .iter()
8090                    .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
8091                {
8092                    continue;
8093                }
8094                let mut equivalent = structured.new_matcher().expect("equivalent");
8095                assert!(equivalent.advance(prefix).expect("equivalent prefix"));
8096                let mut prepared = vec![0; output_len];
8097                equivalent
8098                    .write_mask_le_bytes_into(&handle, Some(&trie), &mut prepared)
8099                    .expect("prepare equivalent");
8100                assert_eq!(prepared, adaptive_out);
8101                assert!(
8102                    equivalent.advance(bytes).expect("emitted token"),
8103                    "emitted {bytes:?} at {prefix:?}"
8104                );
8105            }
8106
8107            let bit = |byte: u8| adaptive_out[byte as usize / 8] & (1 << (byte % 8)) != 0;
8108            match prefix {
8109                br#"{""# => {
8110                    assert!(bit(b'm'));
8111                    assert!(!bit(b'b'));
8112                }
8113                br#"{"method""# => {
8114                    assert!(bit(b':'));
8115                    assert!(!bit(b'}'));
8116                }
8117                br#"{"method":"card""# => {
8118                    assert!(bit(b'}'));
8119                    assert!(!bit(b','));
8120                }
8121                _ => unreachable!(),
8122            }
8123        }
8124
8125        for schema in [
8126            schema,
8127            r#"{"type":"object","required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"additionalProperties":false}"#,
8128        ] {
8129            let program = program(schema);
8130            let mut matcher = program.new_matcher().expect("matcher");
8131            assert!(matcher.advance(br#"{""#).expect("opening key"));
8132            assert!(!matcher.advance(b"b").expect("dead key prefix"), "{schema}");
8133            assert!(
8134                matcher.advance(b"m").expect("rollback preserved state"),
8135                "{schema}"
8136            );
8137
8138            for impossible in [br#"{"bogus""#.as_slice(), br#"{"bogus":"x""#] {
8139                let mut matcher = program.new_matcher().expect("impossible matcher");
8140                assert!(
8141                    !matcher.advance(impossible).expect("impossible prefix"),
8142                    "{schema} admitted {impossible:?}"
8143                );
8144            }
8145
8146            let mut no_trailing_member = program.new_matcher().expect("trailing member matcher");
8147            assert!(no_trailing_member.advance(br#"{"method":"card""#).unwrap());
8148            assert!(
8149                !no_trailing_member.advance(b",").unwrap(),
8150                "{schema} admitted a comma with no viable unseen member"
8151            );
8152            assert!(
8153                no_trailing_member.advance(b"}").unwrap(),
8154                "{schema} did not roll the rejected comma back"
8155            );
8156        }
8157    }
8158
8159    #[cfg(feature = "bench-internals")]
8160    #[test]
8161    fn finite_unevaluated_key_viability_matrix_preserves_branches_intersections_and_seen_keys() {
8162        let mut tokens = FxHashMap::default();
8163        for byte in 0u8..=255 {
8164            tokens.insert(vec![byte], vec![u32::from(byte)]);
8165        }
8166        for (id, token) in [
8167            (256, b"method\":\"card\"".as_slice()),
8168            (257, b"a\\/b\""),
8169            (258, b"\\u00e9\""),
8170        ] {
8171            tokens.insert(token.to_vec(), vec![id]);
8172        }
8173        let vocabulary =
8174            VocabularyHandle::new(Arc::new(build_vocabulary(259, tokens).unwrap())).unwrap();
8175        let trie = TrieCache::new().bind(&vocabulary).unwrap();
8176        let bit = |mask: &[u8], id: usize| mask[id / 8] & (1 << (id % 8)) != 0;
8177
8178        let conditional = program(
8179            r#"{"allOf":[
8180                {"properties":{"alpha":true}},
8181                {"if":{"properties":{"kind":{"const":"x"}},"required":["kind"]},
8182                 "then":{"properties":{"beta":true}},
8183                 "else":{"properties":{"gamma":true}}}
8184            ],"unevaluatedProperties":false}"#,
8185        );
8186        let (mask, nodes) = assert_three_route_mask_and_advance(
8187            &conditional,
8188            &vocabulary,
8189            &trie,
8190            br#"{""#,
8191            "conditional-union",
8192        );
8193        for allowed in [b'a', b'b', b'g', b'k'] {
8194            assert!(
8195                bit(&mask, allowed as usize),
8196                "missing branch prefix {allowed}"
8197            );
8198        }
8199        assert!(!bit(&mask, b'z' as usize));
8200        assert!(nodes < trie.trie().node_count() as u64);
8201
8202        let property_names = program(
8203            r#"{"properties":{"apple":true,"beta":true},"propertyNames":{"pattern":"^a"},"unevaluatedProperties":false}"#,
8204        );
8205        let (mask, _) = assert_three_route_mask_and_advance(
8206            &property_names,
8207            &vocabulary,
8208            &trie,
8209            br#"{""#,
8210            "propertyNames-intersection",
8211        );
8212        assert!(bit(&mask, b'a' as usize));
8213        assert!(!bit(&mask, b'b' as usize));
8214
8215        let value_certificate = program(
8216            r#"{"properties":{"impossible":false,"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8217        );
8218        let (mask, _) = assert_three_route_mask_and_advance(
8219            &value_certificate,
8220            &vocabulary,
8221            &trie,
8222            br#"{""#,
8223            "proven-empty-value",
8224        );
8225        assert!(!bit(&mask, b'i' as usize));
8226        assert!(bit(&mask, b'm' as usize));
8227
8228        let shared_prefix =
8229            program(r#"{"properties":{"a":true,"ab":true},"unevaluatedProperties":false}"#);
8230        let (mask, _) = assert_three_route_mask_and_advance(
8231            &shared_prefix,
8232            &vocabulary,
8233            &trie,
8234            br#"{"a":1,"#,
8235            "seen-terminal-unseen-descendant",
8236        );
8237        assert!(bit(&mask, b'"' as usize), "unseen `ab` still permits a key");
8238        let (mask, _) = assert_three_route_mask_and_advance(
8239            &shared_prefix,
8240            &vocabulary,
8241            &trie,
8242            br#"{"a":1,"a"#,
8243            "seen-terminal-prefix",
8244        );
8245        assert!(bit(&mask, b'b' as usize));
8246        assert!(
8247            !bit(&mask, b'"' as usize),
8248            "duplicate exact `a` must not close"
8249        );
8250        let mut exhausted = shared_prefix.new_matcher().unwrap();
8251        assert!(exhausted.advance(br#"{"a":1,"ab":2"#).unwrap());
8252        assert!(
8253            !exhausted.advance(b",").unwrap(),
8254            "all finite names are seen, so another member cannot begin"
8255        );
8256        assert!(exhausted.advance(b"}").unwrap());
8257
8258        let dependent = program(
8259            r#"{"properties":{"trigger":true},"dependentSchemas":{"trigger":{"properties":{"extra":true}}},"unevaluatedProperties":false}"#,
8260        );
8261        let (mask, _) = assert_three_route_mask_and_advance(
8262            &dependent,
8263            &vocabulary,
8264            &trie,
8265            br#"{""#,
8266            "dependent-schema-union",
8267        );
8268        assert!(bit(&mask, b'e' as usize));
8269        assert!(bit(&mask, b't' as usize));
8270        assert!(!bit(&mask, b'z' as usize));
8271
8272        let encoded_names =
8273            program(r#"{"properties":{"a/b":true,"é":true},"unevaluatedProperties":false}"#);
8274        let (mask, _) = assert_three_route_mask_and_advance(
8275            &encoded_names,
8276            &vocabulary,
8277            &trie,
8278            br#"{"a\/"#,
8279            "escaped-key",
8280        );
8281        assert!(bit(&mask, b'b' as usize));
8282        assert!(!bit(&mask, b'"' as usize));
8283        let (mask, _) = assert_three_route_mask_and_advance(
8284            &encoded_names,
8285            &vocabulary,
8286            &trie,
8287            br#"{"\u00e9"#,
8288            "unicode-escape-key",
8289        );
8290        assert!(bit(&mask, b'"' as usize));
8291        let (mask, _) = assert_three_route_mask_and_advance(
8292            &encoded_names,
8293            &vocabulary,
8294            &trie,
8295            b"{\"\xc3",
8296            "partial-utf8-key",
8297        );
8298        assert!(bit(&mask, 0xa9));
8299
8300        let method = program(
8301            r#"{"required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8302        );
8303        let (mask, _) = assert_three_route_mask_and_advance(
8304            &method,
8305            &vocabulary,
8306            &trie,
8307            br#"{""#,
8308            "quote-colon-value-crossing",
8309        );
8310        assert!(bit(&mask, 256));
8311
8312        // One pattern is a bounded one-DFA product; interacting patterns remain on the exact walker.
8313        let patterned = program(
8314            r#"{"patternProperties":{"^x$":{"type":"string"}},"additionalProperties":false,"unevaluatedProperties":false}"#,
8315        );
8316        let (mask, nodes) = assert_three_route_mask_and_advance(
8317            &patterned,
8318            &vocabulary,
8319            &trie,
8320            br#"{""#,
8321            "single-pattern-bounded-product",
8322        );
8323        assert!(bit(&mask, b'x' as usize));
8324        assert!(!bit(&mask, b'y' as usize));
8325        assert!(nodes < trie.trie().node_count() as u64);
8326        let (mask, _) = assert_three_route_mask_and_advance(
8327            &patterned,
8328            &vocabulary,
8329            &trie,
8330            br#"{"x"#,
8331            "single-pattern-complete-rejection",
8332        );
8333        assert!(bit(&mask, b'"' as usize));
8334        assert!(!bit(&mask, b'y' as usize));
8335
8336        let bounded_pattern = program(
8337            r#"{"patternProperties":{"^[a-z]{2,6}$":{"type":"string"}},"additionalProperties":false,"unevaluatedProperties":false}"#,
8338        );
8339        let (mask, _) = assert_three_route_mask_and_advance(
8340            &bounded_pattern,
8341            &vocabulary,
8342            &trie,
8343            br#"{"amount":"USD","amoun"#,
8344            "seen-pattern-key-terminal-prefix",
8345        );
8346        assert!(
8347            !bit(&mask, b't' as usize),
8348            "a duplicate maximal-length pattern key must not become reachable"
8349        );
8350        let mut duplicate = bounded_pattern.new_matcher().unwrap();
8351        assert!(duplicate.advance(br#"{"amount":"USD","amoun"#).unwrap());
8352        assert!(!duplicate.advance(b"t").unwrap());
8353        assert!(duplicate.advance(b"z").unwrap());
8354
8355        // Exact and pattern-defined keys must make the same duplicate decision under both
8356        // closed-object keywords. This is the permanent public-reproduction four-cell matrix.
8357        for (source, keyword) in [
8358            ("exact", "additionalProperties"),
8359            ("exact", "unevaluatedProperties"),
8360            ("pattern", "additionalProperties"),
8361            ("pattern", "unevaluatedProperties"),
8362        ] {
8363            let declaration = if source == "exact" {
8364                r#""properties":{"amount":{"type":"string"}}"#
8365            } else {
8366                r#""patternProperties":{"^[a-z]{2,6}$":{"type":"string"}}"#
8367            };
8368            let schema = format!(r#"{{"type":"object",{declaration},"{keyword}":false}}"#);
8369            let case = program(&schema);
8370            let mut direct = case.new_matcher().unwrap();
8371            assert!(
8372                !direct.advance(br#"{"amount":"x","amount"#).unwrap(),
8373                "{source}/{keyword} admitted a duplicate key prefix"
8374            );
8375        }
8376
8377        let finite_pattern = program(
8378            r#"{"patternProperties":{"^[ab]$":true},"additionalProperties":false,"unevaluatedProperties":false}"#,
8379        );
8380        let (mask, _) = assert_three_route_mask_and_advance(
8381            &finite_pattern,
8382            &vocabulary,
8383            &trie,
8384            br#"{"a":1,""#,
8385            "finite-pattern-one-unseen-key",
8386        );
8387        assert!(!bit(&mask, b'a' as usize));
8388        assert!(bit(&mask, b'b' as usize));
8389        let mut exhausted_pattern = finite_pattern.new_matcher().unwrap();
8390        assert!(exhausted_pattern.advance(br#"{"a":1,"b":2"#).unwrap());
8391        assert!(!exhausted_pattern.advance(b",").unwrap());
8392        assert!(exhausted_pattern.advance(b"}").unwrap());
8393
8394        let false_exact_intersection = program(
8395            r#"{"properties":{"x":false},"patternProperties":{"^x$":true},"additionalProperties":false}"#,
8396        );
8397        let mut false_exact = false_exact_intersection.new_matcher().unwrap();
8398        assert!(false_exact.advance(br#"{"#).unwrap());
8399        assert!(
8400            !false_exact.advance(br#""x"#).unwrap(),
8401            "a pattern must not revive an exact property whose intersected value schema is false"
8402        );
8403
8404        let escaped_pattern = program(
8405            r#"{"patternProperties":{"^cosk[a-z]$":true},"additionalProperties":false,"unevaluatedProperties":false}"#,
8406        );
8407        let (mask, _) = assert_three_route_mask_and_advance(
8408            &escaped_pattern,
8409            &vocabulary,
8410            &trie,
8411            br#"{"cosk\u"#,
8412            "partial-unicode-escape-key-language",
8413        );
8414        assert!(
8415            bit(&mask, b'0' as usize),
8416            "U+0000..U+0FFF still contains ASCII lowercase completions"
8417        );
8418        assert!(
8419            !bit(&mask, b'2' as usize),
8420            "U+2000..U+2FFF cannot complete an ASCII lowercase pattern key"
8421        );
8422        let mut escaped = escaped_pattern.new_matcher().unwrap();
8423        assert!(escaped.advance(br#"{"cosk\u"#).unwrap());
8424        assert!(!escaped.advance(b"2").unwrap());
8425        assert!(escaped.advance(b"0").unwrap());
8426    }
8427
8428    #[cfg(feature = "bench-internals")]
8429    #[test]
8430    fn admitted_bounded_pattern_key_successors_retain_a_path_to_object_close() {
8431        let schema = program(
8432            r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2,6}$"},"patternProperties":{"^[a-z]{2,6}$":{"type":"string","maxLength":6}},"additionalProperties":false,"maxProperties":2}"#,
8433        );
8434        let prefix = br#"{"amount":"USD","amoun"#;
8435        let records = (0u8..=255)
8436            .map(|byte| (vec![byte], vec![u32::from(byte)]))
8437            .collect::<Vec<_>>();
8438        let mut matcher = schema.new_matcher().unwrap();
8439        assert!(matcher.advance(prefix).unwrap());
8440        let allowed = mask(&mut matcher, 256, &records);
8441        for byte in (0x20u8..=0x7e).chain([b'\t', b'\n']) {
8442            if !allowed.get(crate::primitives::TokenId(u32::from(byte))) {
8443                continue;
8444            }
8445            let tail: &[u8] = match byte {
8446                b'"' => br#":"x"}"#,
8447                b'\\' => br#"u007a":"x"}"#,
8448                b'a'..=b'z' => br#"":"x"}"#,
8449                _ => panic!("unexpected admitted key byte {byte:#04x}"),
8450            };
8451            let mut document = prefix.to_vec();
8452            document.push(byte);
8453            document.extend_from_slice(tail);
8454            let mut completed = schema.new_matcher().unwrap();
8455            assert!(
8456                completed.advance(&document).unwrap() && completed.is_accepting(),
8457                "admitted byte {byte:#04x} left no direct path to object close: {document:?}"
8458            );
8459        }
8460        let mut search_budget = 5_000;
8461        assert_eq!(
8462            closer_first_completion_search(
8463                &schema,
8464                br#"{"amount":"USD","amounz"#,
8465                8,
8466                &mut search_budget,
8467            ),
8468            CompletionSearch::Found,
8469            "the independent closer-first search must find the short closing continuation"
8470        );
8471
8472        // The partially decoded escape witness has a concrete live branch and a proven-dead one.
8473        let escaped =
8474            program(r#"{"patternProperties":{"^cosk[a-z]$":true},"additionalProperties":false}"#);
8475        let mut dead = escaped.new_matcher().unwrap();
8476        assert!(dead.advance(br#"{"cosk\u"#).unwrap());
8477        assert!(!dead.advance(b"2").unwrap());
8478        let mut live = escaped.new_matcher().unwrap();
8479        assert!(live.advance(br#"{"cosk\u0061":1}"#).unwrap());
8480        assert!(live.is_accepting());
8481    }
8482
8483    #[cfg(feature = "bench-internals")]
8484    #[test]
8485    fn finite_unevaluated_key_session_boundary_clears_errors_and_preserves_accounting() {
8486        let program = program(
8487            r#"{"required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8488        );
8489        let records = [b"m".as_slice(), b"b", b"\"", b":", b"method", b"bogus"]
8490            .into_iter()
8491            .enumerate()
8492            .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
8493            .collect();
8494        let vocabulary =
8495            VocabularyHandle::new(Arc::new(build_vocabulary(8, records).unwrap())).unwrap();
8496        let trie = TrieCache::new().bind(&vocabulary).unwrap();
8497        let prefix = br#"{""#;
8498        let exact = minimum_successful_limit(
8499            &program,
8500            &vocabulary,
8501            &trie,
8502            prefix,
8503            program.limits.max_session_bytes,
8504            NearLimitTuning::Session,
8505        );
8506        for limit in around(exact) {
8507            assert_near_limit_three_routes(
8508                &program,
8509                &vocabulary,
8510                &trie,
8511                prefix,
8512                NearLimitTuning::Session(limit),
8513                &format!("finite-key-session-{limit}"),
8514            );
8515        }
8516    }
8517
8518    #[cfg(feature = "bench-internals")]
8519    #[test]
8520    fn contains_unevaluated_product_matrix_matches_all_routes_and_advances() {
8521        struct Case {
8522            name: &'static str,
8523            schema: &'static str,
8524            document: &'static [u8],
8525        }
8526        let cases = [
8527            Case {
8528                name: "min-contains-0",
8529                schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0}"#,
8530                document: b"[1]",
8531            },
8532            Case {
8533                name: "min-contains-1",
8534                schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok$"},"minContains":1}"#,
8535                document: br#"["ok"]"#,
8536            },
8537            Case {
8538                name: "min-contains-2-candidate-dies-then-later-satisfies",
8539                schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"minContains":2,"maxItems":3}"#,
8540                document: br#"["no","okay","ok"]"#,
8541            },
8542            Case {
8543                name: "max-contains-0",
8544                schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0,"maxContains":0}"#,
8545                document: b"[1]",
8546            },
8547            Case {
8548                name: "max-contains-1",
8549                schema: r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":1}"#,
8550                document: br#"["a",1]"#,
8551            },
8552            Case {
8553                name: "max-contains-2",
8554                schema: r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":2}"#,
8555                document: br#"["a","b",1]"#,
8556            },
8557            Case {
8558                name: "prefix-items-to-tail",
8559                schema: r#"{"type":"array","prefixItems":[{"type":"integer"}],"items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
8560                document: br#"[1,"x"]"#,
8561            },
8562            Case {
8563                name: "contains-produces-unevaluated-annotation",
8564                schema: r#"{"prefixItems":[true],"contains":{"type":"string","pattern":"^ok$"},"unevaluatedItems":false}"#,
8565                document: br#"[1,"ok"]"#,
8566            },
8567            Case {
8568                name: "item-accepts-contains-rejects",
8569                schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok$"},"minContains":0}"#,
8570                document: br#"["no"]"#,
8571            },
8572            Case {
8573                name: "contains-accepts-item-rejects-empty-array",
8574                schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0}"#,
8575                document: b"[]",
8576            },
8577            Case {
8578                name: "maximum-length-array",
8579                schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"const":2},"minContains":1,"maxItems":2}"#,
8580                document: b"[1,2]",
8581            },
8582        ];
8583        let token_bytes: &[&[u8]] = &[
8584            b"[", b"]", b",", b"0", b"1", b"2", b"\"", b"a", b"b", b"k", b"n", b"o", b"x", b"y",
8585            b"okay", b"\"ok\"", b"\"no\"",
8586        ];
8587        let records = token_bytes
8588            .iter()
8589            .enumerate()
8590            .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
8591            .collect();
8592        let vocabulary =
8593            VocabularyHandle::new(Arc::new(build_vocabulary(32, records).unwrap())).unwrap();
8594        let trie = TrieCache::new().bind(&vocabulary).unwrap();
8595        let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
8596
8597        for case in cases {
8598            let program = program(case.schema);
8599            let mut adaptive = program.new_matcher().unwrap();
8600            let mut full = program.new_matcher().unwrap();
8601            let mut record = program.new_matcher().unwrap();
8602            for prefix_len in 0..=case.document.len() {
8603                let prefix = &case.document[..prefix_len];
8604                let mut adaptive_out = vec![0; output_len];
8605                let mut full_out = vec![0; output_len];
8606                let mut record_out = vec![0; output_len];
8607                adaptive
8608                    .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
8609                    .unwrap_or_else(|error| panic!("{} {prefix:?}: {error}", case.name));
8610                let MatcherBackend::Incremental(inner) = &mut full.backend else {
8611                    panic!("incremental full matcher")
8612                };
8613                select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
8614                walk_full_trie_mask_for_test(
8615                    inner,
8616                    &vocabulary,
8617                    &trie,
8618                    &mut full_out,
8619                    program.limits,
8620                )
8621                .unwrap();
8622                let MatcherBackend::Incremental(inner) = &mut record.backend else {
8623                    panic!("incremental record matcher")
8624                };
8625                select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
8626                record
8627                    .write_record_mask_le_bytes_into(
8628                        vocabulary.mask_vocab_size(),
8629                        vocabulary.iter_records(),
8630                        &mut record_out,
8631                    )
8632                    .unwrap();
8633                assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, case.name);
8634                assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, case.name);
8635
8636                for (candidate, ids) in vocabulary.iter_records() {
8637                    if !ids
8638                        .iter()
8639                        .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
8640                    {
8641                        continue;
8642                    }
8643                    let mut equivalent = program.new_matcher().unwrap();
8644                    if !prefix.is_empty() {
8645                        assert!(
8646                            equivalent.advance(prefix).unwrap(),
8647                            "{} {prefix:?}",
8648                            case.name
8649                        );
8650                    }
8651                    let mut prepared = vec![0; output_len];
8652                    equivalent
8653                        .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
8654                        .unwrap();
8655                    assert_eq!(prepared, adaptive_out, "{} {prefix:?}", case.name);
8656                    assert!(
8657                        equivalent.advance(candidate).unwrap(),
8658                        "{} emitted {candidate:?} at {prefix:?}",
8659                        case.name
8660                    );
8661                    assert_incremental_accounting(&equivalent, case.name);
8662                }
8663                for matcher in [&adaptive, &full, &record] {
8664                    assert_incremental_accounting(matcher, case.name);
8665                }
8666                if prefix_len < case.document.len() {
8667                    let next = &case.document[prefix_len..prefix_len + 1];
8668                    let (_, ids) = vocabulary
8669                        .iter_records()
8670                        .find(|(bytes, _)| *bytes == next)
8671                        .expect("each document byte is a vocabulary token");
8672                    assert!(ids
8673                        .iter()
8674                        .any(|id| { adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0 }));
8675                    assert!(adaptive.advance(next).unwrap(), "{} {prefix:?}", case.name);
8676                    assert!(full.advance(next).unwrap(), "{} {prefix:?}", case.name);
8677                    assert!(record.advance(next).unwrap(), "{} {prefix:?}", case.name);
8678                }
8679            }
8680        }
8681    }
8682
8683    #[test]
8684    fn unevaluated_accepts_tokens_spanning_multiple_and_nested_boundaries() {
8685        let program = StructuredProgram::compile(ir(
8686            r#"{"properties":{"known":true},"unevaluatedProperties":true}"#,
8687        ))
8688        .expect("program");
8689        let mut whole = program.new_matcher().expect("matcher");
8690        assert_eq!(whole.backend_name(), "incremental");
8691        assert!(whole
8692            .advance(br#"{"known":[1,{"deep":[2,3]}],"extra":{"x":true}}"#)
8693            .unwrap());
8694        assert!(whole.is_accepting());
8695
8696        let mut split = program.new_matcher().expect("matcher");
8697        assert!(split.advance(br#"{"known":"#).unwrap());
8698        assert!(split.advance(br#"[{"deep":[1,{"x":2}]}]"#).unwrap());
8699        assert!(split.advance(br#", "extra":false}"#).unwrap());
8700        assert!(split.is_accepting());
8701
8702        let mut rollback = program.new_matcher().expect("matcher");
8703        assert!(rollback.advance(br#"{"known":[1,2]"#).unwrap());
8704        assert!(!rollback.advance(br#",]}}"#).unwrap());
8705        assert!(rollback.advance(br#", "extra":3}"#).unwrap());
8706        assert!(rollback.is_accepting());
8707    }
8708
8709    fn boolean_vocabulary(eos: u32) -> VocabularyHandle {
8710        let mut tokens = FxHashMap::default();
8711        for b in 0u32..256 {
8712            tokens.insert(vec![b as u8], vec![b]);
8713        }
8714        VocabularyHandle::new(Arc::new(build_vocabulary(eos, tokens).unwrap())).unwrap()
8715    }
8716
8717    #[test]
8718    fn word_mask_is_bit_identical_to_the_byte_mask_including_the_eos_word() {
8719        let program = program(r#"{"not":{"type":"string"}}"#);
8720        let handle = boolean_vocabulary(256);
8721        let mut byte_matcher = program.new_matcher().unwrap();
8722        let mut word_matcher = program.new_matcher().unwrap();
8723
8724        let mut byte_out = vec![0u8; 4 * handle.mask_vocab_size().div_ceil(32)];
8725        byte_matcher
8726            .write_record_mask_le_bytes_into(
8727                handle.mask_vocab_size(),
8728                handle.iter_records(),
8729                &mut byte_out,
8730            )
8731            .unwrap();
8732        let mut word_out = vec![0u32; handle.mask_vocab_size().div_ceil(32)];
8733        word_matcher
8734            .write_record_mask_words_into(
8735                handle.mask_vocab_size(),
8736                handle.iter_records(),
8737                &mut word_out,
8738            )
8739            .unwrap();
8740
8741        let expected: Vec<u32> = byte_out
8742            .chunks_exact(4)
8743            .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
8744            .collect();
8745        assert_eq!(word_out, expected);
8746    }
8747
8748    #[test]
8749    fn word_mask_sets_bit_31_correctly_and_clears_a_dirty_buffer() {
8750        // No JSON value starts with a raw byte, so test bit 31 (byte 63='?') inside a string.
8751        let program = program(r#"{"not":{"type":"integer"}}"#);
8752        let handle = boolean_vocabulary(256);
8753        let mut matcher = program.new_matcher().unwrap();
8754        assert!(matcher.advance(b"\"").unwrap());
8755        let words = handle.mask_vocab_size().div_ceil(32);
8756
8757        let mut out = vec![0xFFFF_FFFFu32; words]; // dirty: every bit set before the call
8758        matcher
8759            .write_record_mask_words_into(handle.mask_vocab_size(), handle.iter_records(), &mut out)
8760            .unwrap();
8761
8762        assert_eq!(
8763            out[1] >> 31 & 1,
8764            1,
8765            "byte 63 ('?') must be legal and land on word 1 bit 31"
8766        );
8767        let vocab_size = handle.mask_vocab_size() as u32;
8768        for (word_index, word) in out.iter().enumerate() {
8769            for bit in 0..32 {
8770                let token_id = word_index as u32 * 32 + bit;
8771                if token_id >= vocab_size {
8772                    assert_eq!(word >> bit & 1, 0, "padding bit {token_id} must stay clear");
8773                }
8774            }
8775        }
8776    }
8777
8778    #[test]
8779    fn word_mask_rejects_wrong_length_without_touching_the_buffer() {
8780        let program = program(r#"{"not":{"type":"string"}}"#);
8781        let handle = boolean_vocabulary(256);
8782        let mut matcher = program.new_matcher().unwrap();
8783
8784        let mut wrong = vec![0xFFFF_FFFFu32; 3];
8785        matcher
8786            .write_record_mask_words_into(
8787                handle.mask_vocab_size(),
8788                handle.iter_records(),
8789                &mut wrong,
8790            )
8791            .unwrap_err();
8792        assert_eq!(
8793            wrong,
8794            vec![0xFFFF_FFFFu32; 3],
8795            "a length-mismatch error must not touch the buffer"
8796        );
8797    }
8798
8799    #[test]
8800    fn word_mask_zero_allocation_hot_path_matches_records_across_many_steps() {
8801        let program = program(
8802            r#"{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"],"additionalProperties":false}"#,
8803        );
8804        let handle = boolean_vocabulary(256);
8805        let mut matcher = program.new_matcher().unwrap();
8806        let mut out = vec![0u32; handle.mask_vocab_size().div_ceil(32)];
8807
8808        for byte in b"{\"ok\":tru" {
8809            matcher
8810                .write_record_mask_words_into(
8811                    handle.mask_vocab_size(),
8812                    handle.iter_records(),
8813                    &mut out,
8814                )
8815                .unwrap();
8816            let bit_set = |id: u32| out[(id / 32) as usize] >> (id % 32) & 1 == 1;
8817            assert!(
8818                bit_set(*byte as u32),
8819                "byte {byte} must be legal at this prefix"
8820            );
8821            assert!(matcher.advance(&[*byte]).unwrap());
8822        }
8823    }
8824}