1use alloc::string::String;
13use alloc::vec::Vec;
14use hekate_core::errors;
15use hekate_core::poly::PolyVariant;
16use hekate_core::trace::{ColumnTrace, ColumnType, Trace, TraceCompatibleField};
17use hekate_math::{Flat, HardwareField, PackableField, TowerField};
18
19use crate::constraint::{BoundaryConstraint, BoundaryTarget, ConstraintAst};
20use crate::expander::VirtualExpander;
21use crate::permutation::{
22 PermutationCheckSpec, RankTable, TableHeight, validate_fixed_selectors, validate_ordered_buses,
23};
24use crate::{Air, FixedColumn, InlineKernelHint, validate_fixed_columns};
25
26#[derive(Clone)]
28pub struct ChipletDef<F: TowerField> {
29 name: String,
30 num_columns: usize,
31 constraint_ast: ConstraintAst<F>,
32 column_layout: Vec<ColumnType>,
33 virtual_column_layout: Vec<ColumnType>,
34 boundary_constraints: Vec<BoundaryConstraint<F>>,
35 fixed_columns: Vec<FixedColumn<F>>,
36 expander: Option<VirtualExpander>,
37 inline_chiplets: Vec<ChipletDef<F>>,
38 inline_kernels: Vec<InlineKernelHint>,
39
40 pub permutation_checks: Vec<(String, PermutationCheckSpec)>,
41}
42
43impl<F: TowerField> ChipletDef<F> {
44 pub fn from_air<P: Air<F> + Send + 'static>(p: &P) -> errors::Result<Self>
47 where
48 F: TraceCompatibleField + PackableField + HardwareField + 'static,
49 <F as PackableField>::Packed: Copy + Send + Sync,
50 {
51 let permutation_checks = p.permutation_checks();
52 for (bus_id, spec) in &permutation_checks {
53 spec.validate_clock_stitching(bus_id)?;
54 }
55
56 let constraint_ast = p.constraint_ast();
57 let boundary_constraints = p.boundary_constraints();
58 let fixed_columns = p.fixed_columns();
59 let inline_chiplets = p.inline_chiplets()?;
60 let inline_kernels = p.inline_chiplet_kernels();
61
62 validate_fixed_selectors(&permutation_checks, &fixed_columns)?;
63 validate_chiplet_boundaries(&boundary_constraints, p.num_columns())?;
64 validate_fixed_columns(&fixed_columns, p.virtual_column_layout(), None)?;
65 validate_ordered_buses(&[RankTable {
66 specs: &permutation_checks,
67 fixed: &fixed_columns,
68 height: TableHeight::Chiplet(None),
69 }])?;
70 validate_expander_coverage(p.virtual_expander(), p.column_layout())?;
71 validate_column_count(p.num_columns(), p.virtual_column_layout().len())?;
72 validate_inline_kernels(
73 &inline_kernels,
74 &inline_chiplets,
75 constraint_ast.roots.len(),
76 p.num_columns(),
77 )?;
78
79 Ok(Self {
80 name: p.name(),
81 num_columns: p.num_columns(),
82 constraint_ast,
83 column_layout: p.column_layout().to_vec(),
84 virtual_column_layout: p.virtual_column_layout().to_vec(),
85 boundary_constraints,
86 fixed_columns,
87 expander: p.virtual_expander().cloned(),
88 inline_chiplets,
89 inline_kernels,
90 permutation_checks,
91 })
92 }
93
94 pub fn ast(&self) -> &ConstraintAst<F> {
96 &self.constraint_ast
97 }
98
99 pub fn boundaries(&self) -> &[BoundaryConstraint<F>] {
100 &self.boundary_constraints
101 }
102
103 pub fn pins(&self) -> &[FixedColumn<F>] {
104 &self.fixed_columns
105 }
106
107 pub fn inline_defs(&self) -> &[ChipletDef<F>] {
108 &self.inline_chiplets
109 }
110
111 pub fn inline_hints(&self) -> &[InlineKernelHint] {
112 &self.inline_kernels
113 }
114
115 pub fn prefix_bus_ids(&mut self, prefix: &str, exempt: &[String]) {
118 for (bus_id, _) in &mut self.permutation_checks {
119 if !exempt.contains(bus_id) {
120 let mut prefixed = String::from(prefix);
121 prefixed.push_str("::");
122 prefixed.push_str(bus_id);
123
124 *bus_id = prefixed;
125 }
126 }
127 }
128
129 pub fn expand_variants<'a>(
132 &self,
133 trace: &'a ColumnTrace,
134 ) -> errors::Result<Vec<PolyVariant<'a, F>>>
135 where
136 F: TraceCompatibleField + 'static,
137 {
138 match &self.expander {
139 Some(e) => e.expand_variants(trace, 0),
140 None => trace.get_poly_variants::<F>(),
141 }
142 }
143
144 #[allow(clippy::too_many_arguments)]
147 pub fn from_wire(
148 name: String,
149 num_columns: usize,
150 constraint_ast: ConstraintAst<F>,
151 column_layout: Vec<ColumnType>,
152 virtual_column_layout: Vec<ColumnType>,
153 boundary_constraints: Vec<BoundaryConstraint<F>>,
154 fixed_columns: Vec<FixedColumn<F>>,
155 expander: Option<VirtualExpander>,
156 permutation_checks: Vec<(String, PermutationCheckSpec)>,
157 inline_chiplets: Vec<ChipletDef<F>>,
158 inline_kernels: Vec<InlineKernelHint>,
159 ) -> errors::Result<Self> {
160 for (bus_id, spec) in &permutation_checks {
161 spec.validate_clock_stitching(bus_id)?;
162 }
163
164 validate_fixed_selectors(&permutation_checks, &fixed_columns)?;
165 validate_chiplet_boundaries(&boundary_constraints, num_columns)?;
166
167 let virt_layout = match &expander {
168 Some(e) => e.virtual_layout(),
169 None => virtual_column_layout.as_slice(),
170 };
171
172 validate_fixed_columns(&fixed_columns, virt_layout, None)?;
173 validate_ordered_buses(&[RankTable {
174 specs: &permutation_checks,
175 fixed: &fixed_columns,
176 height: TableHeight::Chiplet(None),
177 }])?;
178 validate_column_count(num_columns, virt_layout.len())?;
179 validate_inline_kernels(
180 &inline_kernels,
181 &inline_chiplets,
182 constraint_ast.roots.len(),
183 num_columns,
184 )?;
185
186 Ok(Self {
187 inline_chiplets,
188 inline_kernels,
189 ..Self::from_parts(
190 name,
191 num_columns,
192 constraint_ast,
193 column_layout,
194 virtual_column_layout,
195 boundary_constraints,
196 fixed_columns,
197 expander,
198 permutation_checks,
199 )
200 })
201 }
202
203 #[allow(clippy::too_many_arguments)]
206 pub(crate) fn from_parts(
207 name: String,
208 num_columns: usize,
209 constraint_ast: ConstraintAst<F>,
210 column_layout: Vec<ColumnType>,
211 virtual_column_layout: Vec<ColumnType>,
212 boundary_constraints: Vec<BoundaryConstraint<F>>,
213 fixed_columns: Vec<FixedColumn<F>>,
214 expander: Option<VirtualExpander>,
215 permutation_checks: Vec<(String, PermutationCheckSpec)>,
216 ) -> Self {
217 Self {
218 name,
219 num_columns,
220 constraint_ast,
221 column_layout,
222 virtual_column_layout,
223 boundary_constraints,
224 fixed_columns,
225 expander,
226 inline_chiplets: Vec::new(),
227 inline_kernels: Vec::new(),
228 permutation_checks,
229 }
230 }
231}
232
233impl<F: TowerField> Air<F> for ChipletDef<F> {
234 fn name(&self) -> String {
235 self.name.clone()
236 }
237
238 fn num_columns(&self) -> usize {
239 self.num_columns
240 }
241
242 fn boundary_constraints(&self) -> Vec<BoundaryConstraint<F>> {
243 self.boundary_constraints.clone()
244 }
245
246 fn column_layout(&self) -> &[ColumnType] {
247 &self.column_layout
248 }
249
250 fn virtual_column_layout(&self) -> &[ColumnType] {
251 match &self.expander {
252 Some(e) => e.virtual_layout(),
253 None => &self.virtual_column_layout,
254 }
255 }
256
257 fn permutation_checks(&self) -> Vec<(String, PermutationCheckSpec)> {
258 self.permutation_checks.clone()
259 }
260
261 fn fixed_columns(&self) -> Vec<FixedColumn<F>> {
262 self.fixed_columns.clone()
263 }
264
265 fn virtual_expander(&self) -> Option<&VirtualExpander> {
266 self.expander.as_ref()
267 }
268
269 fn parse_virtual_row(&self, bytes: &[u8], res: &mut Vec<Flat<F>>)
270 where
271 F: TraceCompatibleField,
272 {
273 if let Some(e) = &self.expander {
274 res.clear();
275
276 e.parse_row(bytes, res)
277 .expect("committed row byte length must match physical_row_bytes");
278 return;
279 }
280
281 res.clear();
282
283 let mut offset = 0;
284 for col_type in &self.column_layout {
285 let size = col_type.byte_size();
286 if offset + size <= bytes.len() {
287 res.push(col_type.parse_from_bytes(&bytes[offset..offset + size]));
288 offset += size;
289 }
290 }
291 }
292
293 fn constraint_ast(&self) -> ConstraintAst<F> {
294 self.constraint_ast.clone()
295 }
296
297 fn inline_chiplets(&self) -> errors::Result<Vec<ChipletDef<F>>> {
298 Ok(self.inline_chiplets.clone())
299 }
300
301 fn inline_chiplet_kernels(&self) -> Vec<InlineKernelHint> {
302 self.inline_kernels.clone()
303 }
304}
305
306fn validate_chiplet_boundaries<F>(
310 boundaries: &[BoundaryConstraint<F>],
311 num_columns: usize,
312) -> errors::Result<()> {
313 for bc in boundaries {
314 if bc.col_idx >= num_columns {
315 return Err(errors::Error::Protocol {
316 protocol: "boundary",
317 message: "chiplet boundary col_idx out of range",
318 });
319 }
320
321 if matches!(bc.target, BoundaryTarget::PublicInput(_)) {
322 return Err(errors::Error::Protocol {
323 protocol: "boundary",
324 message: "chiplet boundaries must use BoundaryTarget::Constant",
325 });
326 }
327 }
328
329 Ok(())
330}
331
332fn validate_expander_coverage(
335 expander: Option<&VirtualExpander>,
336 layout: &[ColumnType],
337) -> errors::Result<()> {
338 let covered = match expander {
339 Some(e) => e.num_physical_columns(),
340 None => layout.len(),
341 };
342
343 if covered != layout.len() {
344 return Err(errors::Error::Protocol {
345 protocol: "chiplet",
346 message: "virtual_expander does not tile column_layout",
347 });
348 }
349
350 Ok(())
351}
352
353fn validate_column_count(num_columns: usize, virtual_columns: usize) -> errors::Result<()> {
354 if num_columns != virtual_columns {
355 return Err(errors::Error::Protocol {
356 protocol: "chiplet",
357 message: "num_columns does not match the virtual column layout",
358 });
359 }
360
361 Ok(())
362}
363
364fn validate_inline_kernels<F: TowerField>(
365 hints: &[InlineKernelHint],
366 chiplets: &[ChipletDef<F>],
367 num_roots: usize,
368 num_columns: usize,
369) -> errors::Result<()> {
370 for hint in hints {
371 let inside = chiplets.get(hint.chiplet_idx).is_some_and(|cd| {
372 let roots_end = hint.root_offset.checked_add(cd.constraint_ast.roots.len());
373 let columns_end = hint.column_offset.checked_add(cd.num_columns);
374
375 roots_end.is_some_and(|end| end <= num_roots)
376 && columns_end.is_some_and(|end| end <= num_columns)
377 });
378
379 if !inside {
380 return Err(errors::Error::Protocol {
381 protocol: "chiplet",
382 message: "inline kernel hint outside the snapshot",
383 });
384 }
385 }
386
387 Ok(())
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::constraint::builder::ConstraintSystem;
394 use crate::define_columns;
395 use crate::permutation::{
396 BusKind, ChallengeLabel, EMIT_RANK_LABEL, PermutationCheckSpec, Side, Source,
397 };
398 use crate::{ConstraintAst, FixedShape};
399 use alloc::string::String;
400 use alloc::vec;
401 use hekate_core::trace::ColumnType;
402 use hekate_math::Block128;
403
404 type F = Block128;
405
406 define_columns! {
407 PairedAirCols {
408 KEY: B32,
409 S_SEND: Bit,
410 S_RECV: Bit,
411 }
412 }
413
414 #[derive(Clone)]
415 struct ExpanderAir {
416 expander: VirtualExpander,
417 }
418
419 impl Air<F> for ExpanderAir {
420 fn num_columns(&self) -> usize {
421 self.expander.virtual_layout().len()
422 }
423
424 fn column_layout(&self) -> &[ColumnType] {
425 &[ColumnType::B32, ColumnType::Bit]
426 }
427
428 fn virtual_expander(&self) -> Option<&VirtualExpander> {
429 Some(&self.expander)
430 }
431
432 fn constraint_ast(&self) -> ConstraintAst<F> {
433 ConstraintSystem::<F>::new().build()
434 }
435 }
436
437 #[derive(Clone)]
438 struct OneBusAir {
439 spec: PermutationCheckSpec,
440 }
441
442 impl Air<F> for OneBusAir {
443 fn num_columns(&self) -> usize {
444 2
445 }
446
447 fn column_layout(&self) -> &[ColumnType] {
448 &[ColumnType::B32, ColumnType::Bit]
449 }
450
451 fn permutation_checks(&self) -> Vec<(String, PermutationCheckSpec)> {
452 vec![("test_bus".into(), self.spec.clone())]
453 }
454
455 fn fixed_columns(&self) -> Vec<FixedColumn<F>> {
456 liveness_pin(1)
457 }
458
459 fn constraint_ast(&self) -> ConstraintAst<F> {
460 ConstraintSystem::<F>::new().build()
461 }
462 }
463
464 #[derive(Clone)]
465 struct PairedAir {
466 pinned: bool,
467 }
468
469 impl Air<F> for PairedAir {
470 fn num_columns(&self) -> usize {
471 PairedAirCols::NUM_COLUMNS
472 }
473
474 fn column_layout(&self) -> &[ColumnType] {
475 static LAYOUT: std::sync::OnceLock<Vec<ColumnType>> = std::sync::OnceLock::new();
476 LAYOUT.get_or_init(PairedAirCols::build_layout)
477 }
478
479 fn permutation_checks(&self) -> Vec<(String, PermutationCheckSpec)> {
480 let sources = vec![
481 (Source::Column(PairedAirCols::KEY), b"k_a" as ChallengeLabel),
482 (Source::RowIndexLeBytes(4), b"k_clk" as ChallengeLabel),
483 ];
484
485 vec![(
486 "paired_test_bus".into(),
487 PermutationCheckSpec::new_paired(
488 sources,
489 PairedAirCols::S_SEND,
490 PairedAirCols::S_RECV,
491 BusKind::Permutation,
492 ),
493 )]
494 }
495
496 fn fixed_columns(&self) -> Vec<FixedColumn<F>> {
497 match self.pinned {
498 true => vec![
499 FixedColumn::sparse(PairedAirCols::S_SEND, vec![(0, F::ONE)]),
500 FixedColumn::sparse(PairedAirCols::S_RECV, vec![(1, F::ONE)]),
501 ],
502 false => Vec::new(),
503 }
504 }
505
506 fn constraint_ast(&self) -> ConstraintAst<F> {
507 ConstraintSystem::<F>::new().build()
508 }
509 }
510
511 #[derive(Clone)]
512 struct HintedAir {
513 hints: Vec<InlineKernelHint>,
514 }
515
516 impl Air<F> for HintedAir {
517 fn num_columns(&self) -> usize {
518 2
519 }
520
521 fn column_layout(&self) -> &[ColumnType] {
522 &[ColumnType::B32, ColumnType::Bit]
523 }
524
525 fn constraint_ast(&self) -> ConstraintAst<F> {
526 let cs = ConstraintSystem::<F>::new();
527 cs.assert_boolean(cs.col(1));
528
529 cs.build()
530 }
531
532 fn inline_chiplets(&self) -> errors::Result<Vec<ChipletDef<F>>> {
533 match self.hints.is_empty() {
534 true => Ok(Vec::new()),
535 false => Ok(vec![ChipletDef::from_air(&HintedAir {
536 hints: Vec::new(),
537 })?]),
538 }
539 }
540
541 fn inline_chiplet_kernels(&self) -> Vec<InlineKernelHint> {
542 self.hints.clone()
543 }
544 }
545
546 fn key_only() -> Vec<(Source, ChallengeLabel)> {
547 vec![(Source::Column(0), b"k_a")]
548 }
549
550 fn key_with_clock() -> Vec<(Source, ChallengeLabel)> {
551 vec![
552 (Source::Column(0), b"k_a"),
553 (Source::RowIndexLeBytes(4), b"k_clk"),
554 ]
555 }
556
557 fn key_with_rank() -> Vec<(Source, ChallengeLabel)> {
558 vec![
559 (Source::Column(0), b"k_a"),
560 (Source::EmitRank(Side::Response), EMIT_RANK_LABEL),
561 ]
562 }
563
564 fn snapshot(spec: PermutationCheckSpec) -> errors::Result<ChipletDef<F>> {
565 ChipletDef::from_air(&OneBusAir { spec })
566 }
567
568 fn assert_logup_bus_err<T>(res: errors::Result<T>) {
569 match res {
570 Err(errors::Error::Protocol { protocol, .. }) => {
571 assert_eq!(protocol, "logup_bus");
572 }
573 Ok(_) => panic!("expected Err(Protocol {{ protocol: \"logup_bus\", .. }})"),
574 Err(other) => panic!("expected Err(Protocol), got {:?}", other),
575 }
576 }
577
578 fn liveness_pin(col: usize) -> Vec<FixedColumn<F>> {
579 vec![FixedColumn::sparse(col, vec![(0, F::ONE)])]
580 }
581
582 #[test]
583 fn def_rejects_permutation_without_clock() {
584 let spec = PermutationCheckSpec::new(key_only(), Some(1));
585 assert_logup_bus_err(snapshot(spec));
586 }
587
588 #[test]
589 fn def_rejects_permutation_with_empty_waiver() {
590 let spec = PermutationCheckSpec::new(key_only(), Some(1)).with_clock_waiver("");
591 assert_logup_bus_err(snapshot(spec));
592 }
593
594 #[test]
595 fn def_rejects_permutation_with_clock_and_waiver() {
596 let spec =
597 PermutationCheckSpec::new(key_with_clock(), Some(1)).with_clock_waiver("redundant");
598 assert_logup_bus_err(snapshot(spec));
599 }
600
601 #[test]
602 fn def_rejects_lookup_with_waiver() {
603 let spec = PermutationCheckSpec::new_lookup(key_only(), Some(1)).with_clock_waiver("nope");
604 assert_logup_bus_err(snapshot(spec));
605 }
606
607 #[test]
608 fn def_accepts_permutation_with_row_index() {
609 let spec = PermutationCheckSpec::new(key_with_clock(), Some(1));
610 snapshot(spec).expect("permutation bus with row-index source must accept");
611 }
612
613 #[test]
614 fn def_accepts_permutation_with_clock_waiver() {
615 let spec = PermutationCheckSpec::new(key_only(), Some(1))
616 .with_clock_waiver("see foo.rs:42: structurally unique by AIR body");
617 snapshot(spec).expect("permutation bus with non-empty clock_waiver must accept");
618 }
619
620 #[test]
621 fn def_accepts_lookup_without_clock() {
622 let spec = PermutationCheckSpec::new_lookup(key_only(), Some(1));
623 snapshot(spec).expect("lookup bus without clock must accept");
624 }
625
626 #[test]
627 fn def_rejects_permutation_with_too_short_waiver() {
628 let spec = PermutationCheckSpec::new(key_only(), Some(1)).with_clock_waiver("see x.rs");
629 assert_logup_bus_err(snapshot(spec));
630 }
631
632 #[test]
633 fn def_rejects_permutation_with_missing_see_citation() {
634 let spec = PermutationCheckSpec::new(key_only(), Some(1))
635 .with_clock_waiver("structurally unique by AIR body but no file citation prefix here");
636 assert_logup_bus_err(snapshot(spec));
637 }
638
639 #[test]
640 fn chiplet_def_rejects_paired_spec_with_witness_selectors() {
641 assert_logup_bus_err(ChipletDef::<F>::from_air(&PairedAir { pinned: false }));
642 }
643
644 #[test]
645 fn chiplet_def_accepts_paired_spec_with_fixed_selectors() {
646 ChipletDef::<F>::from_air(&PairedAir { pinned: true })
647 .expect("paired AIR with fixed selectors must snapshot");
648 }
649
650 #[test]
651 fn chiplet_def_requires_expander_to_tile_the_layout() {
652 let tiled = ExpanderAir {
653 expander: VirtualExpander::new()
654 .expand_bits(1, ColumnType::B32)
655 .control_bits(1)
656 .build()
657 .unwrap(),
658 };
659
660 ChipletDef::<F>::from_air(&tiled).expect("tiling expander must snapshot");
661
662 let short = ExpanderAir {
663 expander: VirtualExpander::new()
664 .expand_bits(1, ColumnType::B32)
665 .build()
666 .unwrap(),
667 };
668
669 assert!(ChipletDef::<F>::from_air(&short).is_err());
670 }
671
672 #[test]
673 fn chiplet_def_rejects_hint_outside_the_snapshot() {
674 let hint = |chiplet_idx, root_offset, column_offset| HintedAir {
675 hints: vec![InlineKernelHint {
676 chiplet_idx,
677 root_offset,
678 column_offset,
679 }],
680 };
681
682 let def = ChipletDef::<F>::from_air(&hint(0, 0, 0)).unwrap();
683
684 assert_eq!(def.inline_chiplet_kernels(), hint(0, 0, 0).hints);
685
686 for stray in [
687 hint(1, 0, 0),
688 hint(0, 1, 0),
689 hint(0, 0, 1),
690 hint(0, usize::MAX, 0),
691 ] {
692 assert!(ChipletDef::<F>::from_air(&stray).is_err());
693 }
694 }
695
696 #[test]
697 fn validator_rejects_recv_selector_without_send_selector() {
698 let spec = PermutationCheckSpec {
699 sources: vec![
700 (Source::Column(PairedAirCols::KEY), b"k_a" as ChallengeLabel),
701 (Source::RowIndexLeBytes(4), b"k_clk" as ChallengeLabel),
702 ],
703 selector: None,
704 recv_selector: Some(PairedAirCols::S_RECV),
705 kind: BusKind::Permutation,
706 clock_waiver: None,
707 };
708
709 assert_logup_bus_err(validate_fixed_selectors::<F>(
710 &[("asym_bus".into(), spec)],
711 &liveness_pin(PairedAirCols::S_RECV),
712 ));
713 }
714
715 #[test]
716 fn def_rejects_witness_selector() {
717 let spec = PermutationCheckSpec::new(key_with_clock(), Some(0));
718 assert_logup_bus_err(snapshot(spec));
719 }
720
721 #[test]
722 fn def_accepts_absent_selector() {
723 let spec = PermutationCheckSpec::new(key_with_clock(), None);
724 snapshot(spec).expect("selector-free bus must accept");
725 }
726
727 #[test]
728 fn validator_rejects_selector_pinned_to_substituted_shape() {
729 let spec = PermutationCheckSpec::new(key_with_clock(), Some(1));
730
731 for shape in [
732 FixedShape::FirstRow,
733 FixedShape::LastRow,
734 FixedShape::Custom(vec![true, false]),
735 ] {
736 assert_logup_bus_err(validate_fixed_selectors(
737 &[("svc".into(), spec.clone())],
738 &[FixedColumn::<F> { col_idx: 1, shape }],
739 ));
740 }
741 }
742
743 #[test]
744 fn validator_accepts_selector_pinned_to_overlay_shape() {
745 let spec = PermutationCheckSpec::new(key_with_clock(), Some(1));
746
747 validate_fixed_selectors(&[("svc".into(), spec)], &liveness_pin(1))
748 .expect("overlay-pinned selector must accept");
749 }
750
751 #[test]
752 fn from_wire_rejects_hint_outside_snapshot() {
753 let bare = HintedAir { hints: Vec::new() };
754
755 let wire = |chiplet_idx, root_offset| {
756 ChipletDef::<F>::from_wire(
757 String::from("wired"),
758 2,
759 bare.constraint_ast(),
760 bare.column_layout().to_vec(),
761 bare.column_layout().to_vec(),
762 Vec::new(),
763 Vec::new(),
764 None,
765 Vec::new(),
766 vec![ChipletDef::from_air(&bare).unwrap()],
767 vec![InlineKernelHint {
768 chiplet_idx,
769 root_offset,
770 column_offset: 0,
771 }],
772 )
773 };
774
775 assert!(wire(0, 0).is_ok());
776 assert!(wire(1, 0).is_err());
777 assert!(wire(0, 1).is_err());
778 }
779
780 #[test]
781 fn def_runs_ordered_bus_predicate() {
782 snapshot(PermutationCheckSpec::new(key_with_rank(), Some(1))).unwrap();
783
784 let lookup = PermutationCheckSpec::new_lookup(key_with_rank(), Some(1));
785
786 assert_logup_bus_err(snapshot(lookup.clone()));
787
788 let air = OneBusAir { spec: lookup };
789
790 assert_logup_bus_err(ChipletDef::<F>::from_wire(
791 String::from("wired"),
792 2,
793 air.constraint_ast(),
794 air.column_layout().to_vec(),
795 air.column_layout().to_vec(),
796 Vec::new(),
797 air.fixed_columns(),
798 None,
799 Air::<F>::permutation_checks(&air),
800 Vec::new(),
801 Vec::new(),
802 ));
803 }
804}