1use std::collections::{HashMap, HashSet, VecDeque};
2use std::sync::Arc;
3
4#[cfg(doc)]
5use khive_gate::GateRequest;
6use khive_gate::{AllowAllGate, GateRef};
7use khive_storage::EventStore;
8#[cfg(doc)]
9use khive_storage::EventView;
10use khive_types::{Namespace, NotePropertyPolicySpec};
11use serde_json::Value;
12
13use crate::error::{
14 CircularPackDependency, MissingPackDependencies, MissingPackDependency, RuntimeError,
15};
16use crate::KhiveRuntime;
17
18use super::{
19 DispatchHook, EdgeEndpointRule, EntityTypeDef, HandlerDef, PackByIdResolver, PackRuntime,
20 VerbCategory, VerbRegistry, Visibility, RESERVED_ENVELOPE_ARGS,
21};
22#[cfg(doc)]
23use super::{PackFactory, PackRegistry};
24
25pub struct VerbRegistryBuilder {
30 packs: Vec<Box<dyn PackRuntime>>,
31 pub(super) pack_versions: HashMap<String, &'static str>,
32 pack_trusted: Vec<bool>,
43 disabled_verbs: HashMap<String, Vec<String>>,
44 resolvers: Vec<(String, Box<dyn PackByIdResolver>)>,
45 pub(super) kg_read_resolver: Option<Arc<crate::kg_read::KgReadResolver>>,
46 gate: GateRef,
47 default_namespace: String,
48 visible_namespaces: Vec<Namespace>,
56 actor_id: Option<String>,
59 event_store: Option<Arc<dyn EventStore>>,
66 runtime_event_store: Option<KhiveRuntime>,
68 audit_store_read_only: bool,
71 dispatch_hook: Option<Arc<dyn DispatchHook>>,
77 audit_batch_config: Option<crate::audit_batch::AuditBatchConfig>,
81}
82
83impl VerbRegistryBuilder {
84 pub fn new() -> Self {
86 Self {
87 packs: Vec::new(),
88 pack_versions: HashMap::new(),
89 pack_trusted: Vec::new(),
90 disabled_verbs: HashMap::new(),
91 resolvers: Vec::new(),
92 kg_read_resolver: None,
93 gate: std::sync::Arc::new(AllowAllGate),
94 default_namespace: Namespace::local().as_str().to_string(),
95 visible_namespaces: vec![],
96 actor_id: None,
97 event_store: None,
98 runtime_event_store: None,
99 audit_store_read_only: false,
100 dispatch_hook: None,
101 audit_batch_config: None,
102 }
103 }
104
105 pub fn with_disabled_verbs(&mut self, pack: &str, verbs: &[String]) -> &mut Self {
107 self.disabled_verbs.insert(pack.to_owned(), verbs.to_vec());
108 self
109 }
110
111 pub fn with_visible_namespaces(&mut self, ns: Vec<Namespace>) -> &mut Self {
119 self.visible_namespaces = ns;
120 self
121 }
122
123 pub fn with_actor_id(&mut self, actor_id: Option<String>) -> &mut Self {
130 self.actor_id = actor_id;
131 self
132 }
133
134 pub fn register<P: khive_types::Pack + PackRuntime + 'static>(&mut self, pack: P) -> &mut Self {
144 self.packs.push(Box::new(pack));
145 self.pack_trusted.push(false);
146 self
147 }
148
149 pub(crate) fn register_boxed(&mut self, pack: Box<dyn PackRuntime>) -> &mut Self {
162 self.packs.push(pack);
163 self.pack_trusted.push(true);
164 self
165 }
166
167 pub fn register_mounted(
169 &mut self,
170 pack: Box<dyn PackRuntime>,
171 ) -> Result<&mut Self, RuntimeError> {
172 if pack.mounted_namespace() != Some(pack.name()) || !pack.handlers().is_empty() {
173 return Err(RuntimeError::InvalidInput(
174 "invalid mounted namespace registration".into(),
175 ));
176 }
177 self.packs.push(pack);
178 self.pack_trusted.push(false);
179 Ok(self)
180 }
181
182 #[cfg(any(test, feature = "test-internals"))]
191 pub fn register_trusted<P: khive_types::Pack + PackRuntime + 'static>(
192 &mut self,
193 pack: P,
194 ) -> &mut Self {
195 self.packs.push(Box::new(pack));
196 self.pack_trusted.push(true);
197 self
198 }
199
200 pub fn register_resolver(
205 &mut self,
206 name: impl Into<String>,
207 resolver: Box<dyn PackByIdResolver>,
208 ) -> &mut Self {
209 self.resolvers.push((name.into(), resolver));
210 self
211 }
212
213 pub fn with_gate(&mut self, gate: GateRef) -> &mut Self {
219 self.gate = gate;
220 self
221 }
222
223 pub fn with_default_namespace(&mut self, ns: impl Into<String>) -> &mut Self {
228 self.default_namespace = ns.into();
229 self
230 }
231
232 pub fn with_event_store(&mut self, store: Arc<dyn EventStore>) -> &mut Self {
242 self.event_store = Some(store);
243 self.runtime_event_store = None;
244 self.audit_store_read_only = false;
245 self
246 }
247
248 pub fn with_runtime_event_store(
263 &mut self,
264 runtime: &KhiveRuntime,
265 ) -> Result<&mut Self, RuntimeError> {
266 self.event_store = None;
267 self.runtime_event_store = Some(runtime.clone());
268 self.audit_store_read_only = false;
269 Ok(self)
270 }
271
272 pub fn with_audit_batch_config(
279 &mut self,
280 config: crate::audit_batch::AuditBatchConfig,
281 ) -> &mut Self {
282 self.audit_batch_config = Some(config);
283 self
284 }
285
286 pub fn with_read_only_audit_store(&mut self) -> &mut Self {
292 self.event_store = None;
293 self.runtime_event_store = None;
294 self.audit_store_read_only = true;
295 self
296 }
297
298 pub fn with_dispatch_hook(&mut self, hook: Arc<dyn DispatchHook>) -> &mut Self {
309 self.dispatch_hook = Some(hook);
310 self
311 }
312
313 pub fn build(self) -> Result<VerbRegistry, RuntimeError> {
319 self.build_registry(true)
320 }
321
322 pub fn build_metadata(mut self) -> Result<PackMetadataRegistry, RuntimeError> {
325 self.event_store = None;
326 self.runtime_event_store = None;
327 self.dispatch_hook = None;
328 self.resolvers.clear();
329 self.build_registry(false)
330 .map(|registry| PackMetadataRegistry { registry })
331 }
332
333 fn build_registry(self, activate: bool) -> Result<VerbRegistry, RuntimeError> {
334 let packs = self.packs;
335 let mut name_to_idx: HashMap<&str, usize> = HashMap::with_capacity(packs.len());
336 for (idx, pack) in packs.iter().enumerate() {
337 if let Some(prev_idx) = name_to_idx.insert(pack.name(), idx) {
338 return Err(RuntimeError::PackRedeclared {
339 name: pack.name().to_string(),
340 first_idx: prev_idx,
341 second_idx: idx,
342 });
343 }
344 }
345
346 for mounted in packs
347 .iter()
348 .filter(|pack| pack.mounted_namespace().is_some())
349 {
350 let prefix = format!("{}.", mounted.name());
351 if packs
352 .iter()
353 .flat_map(|pack| pack.handlers())
354 .any(|handler| handler.name.starts_with(&prefix))
355 {
356 return Err(RuntimeError::InvalidInput(
357 "mounted namespace collides with a native verb".into(),
358 ));
359 }
360 }
361
362 for pack in &packs {
366 for handler in pack.handlers() {
367 for parameter in handler.params {
368 if RESERVED_ENVELOPE_ARGS.contains(¶meter.name) {
369 return Err(RuntimeError::ReservedEnvelopeParam {
370 pack: pack.name().to_string(),
371 verb: handler.name.to_string(),
372 param: parameter.name.to_string(),
373 });
374 }
375 }
376 }
377 }
378
379 let mut missing: Vec<MissingPackDependency> = Vec::new();
380 let mut indegree = vec![0usize; packs.len()];
381 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); packs.len()];
382
383 for (idx, pack) in packs.iter().enumerate() {
384 for &requires in pack.requires() {
385 match name_to_idx.get(requires).copied() {
386 Some(dep_idx) => {
387 dependents[dep_idx].push(idx);
388 indegree[idx] += 1;
389 }
390 None => missing.push(MissingPackDependency {
391 from: pack.name().to_string(),
392 requires: requires.to_string(),
393 }),
394 }
395 }
396 }
397
398 if !missing.is_empty() {
399 return if missing.len() == 1 {
400 Err(RuntimeError::MissingPackDependency(missing.remove(0)))
401 } else {
402 Err(RuntimeError::MissingPackDependencies(
403 MissingPackDependencies { missing },
404 ))
405 };
406 }
407
408 let mut ready: VecDeque<usize> = indegree
409 .iter()
410 .enumerate()
411 .filter_map(|(idx, degree)| (*degree == 0).then_some(idx))
412 .collect();
413 let mut ordered_indices = Vec::with_capacity(packs.len());
414
415 while let Some(idx) = ready.pop_front() {
416 ordered_indices.push(idx);
417 for &dep_idx in &dependents[idx] {
418 indegree[dep_idx] -= 1;
419 if indegree[dep_idx] == 0 {
420 ready.push_back(dep_idx);
421 }
422 }
423 }
424
425 if ordered_indices.len() != packs.len() {
426 let cycle_nodes: HashSet<usize> = indegree
427 .iter()
428 .enumerate()
429 .filter_map(|(idx, degree)| (*degree > 0).then_some(idx))
430 .collect();
431 let cycle = find_pack_dependency_cycle(&packs, &name_to_idx, &cycle_nodes);
432 return Err(RuntimeError::CircularPackDependency(
433 CircularPackDependency { cycle },
434 ));
435 }
436
437 let mut pack_slots: Vec<Option<Box<dyn PackRuntime>>> =
438 packs.into_iter().map(Some).collect();
439 let mut trusted_slots: Vec<Option<bool>> =
440 self.pack_trusted.into_iter().map(Some).collect();
441 let mut ordered_packs: Vec<Box<dyn PackRuntime>> = Vec::with_capacity(pack_slots.len());
442 let mut ordered_trusted: Vec<bool> = Vec::with_capacity(trusted_slots.len());
443 for idx in ordered_indices {
444 ordered_packs.push(
445 pack_slots[idx]
446 .take()
447 .expect("topological index must exist"),
448 );
449 ordered_trusted.push(
450 trusted_slots[idx]
451 .take()
452 .expect("topological index must exist"),
453 );
454 }
455
456 validate_unique_note_kinds(&ordered_packs)?;
457 validate_unique_verb_names(&ordered_packs)?;
458 validate_unique_entity_types(&ordered_packs)?;
459 validate_entity_type_note_kind_collisions(&ordered_packs)?;
460 validate_brain_consumer_kinds(&ordered_packs)?;
461 let mut disabled_verbs = HashSet::new();
462 let mut policies: Vec<_> = self.disabled_verbs.iter().collect();
463 policies.sort_by_key(|(pack, _)| *pack);
464 for (name, verbs) in policies {
465 if verbs.is_empty() {
466 continue;
467 }
468 let pack = ordered_packs
469 .iter()
470 .find(|pack| pack.name() == name.as_str())
471 .ok_or_else(|| {
472 RuntimeError::InvalidInput(format!(
473 "packs.{name}.verbs_disabled: pack is not loaded"
474 ))
475 })?;
476 for verb in verbs {
477 let handler = pack
478 .handlers()
479 .iter()
480 .find(|handler| {
481 handler.name == verb.as_str()
482 && matches!(handler.visibility, Visibility::Verb)
483 })
484 .ok_or_else(|| {
485 RuntimeError::InvalidInput(format!(
486 "packs.{name}.verbs_disabled: {verb:?} is not a public verb owned by this pack"
487 ))
488 })?;
489 disabled_verbs.insert(handler.name);
490 }
491 }
492 let note_property_policies = collect_note_property_policies(&ordered_packs)?;
493 if activate {
494 for pack in &ordered_packs {
495 pack.validate_config()?;
496 }
497 }
498
499 let available_verbs: Vec<&'static str> = ordered_packs
500 .iter()
501 .flat_map(|p| p.handlers().iter())
502 .filter(|h| {
503 matches!(h.visibility, Visibility::Verb) && !disabled_verbs.contains(h.name)
504 })
505 .map(|h| h.name)
506 .collect();
507
508 let mut handler_by_name: HashMap<&'static str, &'static HandlerDef> = HashMap::new();
512 for pack in &ordered_packs {
513 for handler in pack.handlers() {
514 handler_by_name.entry(handler.name).or_insert(handler);
515 }
516 }
517
518 let mut degrade_safe_verbs: HashSet<&'static str> = HashSet::new();
530 let mut read_replay_safe_verbs = HashSet::new();
531 for (pack, &trusted) in ordered_packs.iter().zip(ordered_trusted.iter()) {
532 if !trusted {
533 continue;
534 }
535 let pack_name = pack.name();
536 for handler in pack.handlers() {
537 let canonical_owner = handler
538 .name
539 .split_once('.')
540 .map_or("kg", |(owner, _)| owner);
541 if matches!(handler.visibility, Visibility::Verb)
542 && pack_name == canonical_owner
543 && crate::classify_operation(handler.name) == Some(crate::OperationAccess::Read)
544 && !VerbRegistry::SIDE_EFFECTING_ASSERTIVE_VERBS.contains(&handler.name)
545 {
546 read_replay_safe_verbs.insert(handler.name);
547 }
548 if !matches!(handler.visibility, Visibility::Verb)
549 || handler.category != VerbCategory::Assertive
550 {
551 continue;
552 }
553 let eligible = VerbRegistry::admission_degrade_safe_sorted()
554 .binary_search_by(|&(p, v)| p.cmp(pack_name).then_with(|| v.cmp(handler.name)))
555 .is_ok();
556 if eligible {
557 degrade_safe_verbs.insert(handler.name);
558 }
559 }
560 }
561
562 let event_store = match self.runtime_event_store {
574 Some(runtime) => Some(runtime.raw_events_for_namespace(&self.default_namespace)?),
575 None => self.event_store,
576 };
577 if let Some(store) = &event_store {
578 if !store.supports_idempotent_audit_batch() {
579 return Err(RuntimeError::IncompatibleEventStore(
580 "the configured EventStore does not implement ADR-133's \
581 preflight_event/append_events_idempotent pair \
582 (supports_idempotent_audit_batch() returned false); every \
583 audited dispatch would silently lose its audit row while \
584 still reporting success. Implement both methods and \
585 override supports_idempotent_audit_batch() to opt in, or \
586 do not call with_event_store() for this backend."
587 .to_string(),
588 ));
589 }
590 }
591 let audit_batch = event_store.clone().map(|store| {
592 crate::audit_batch::AuditBatch::new(
593 store,
594 self.audit_batch_config.clone().unwrap_or_default(),
595 )
596 });
597
598 let attributed_edge_rules = ordered_packs
602 .iter()
603 .flat_map(|pack| {
604 pack.edge_rules()
605 .iter()
606 .map(move |rule| (pack.name().to_owned(), *rule))
607 })
608 .collect();
609
610 Ok(VerbRegistry {
611 packs: Arc::new(ordered_packs),
612 attributed_edge_rules: Arc::new(attributed_edge_rules),
613 pack_versions: Arc::new(self.pack_versions),
614 note_property_policies: Arc::new(note_property_policies),
615 resolvers: Arc::new(self.resolvers),
616 kg_read_resolver: self.kg_read_resolver,
617 gate: self.gate,
618 default_namespace: self.default_namespace,
619 visible_namespaces: self.visible_namespaces,
620 actor_id: self.actor_id,
621 event_store,
622 audit_store_read_only: self.audit_store_read_only,
623 dispatch_hook: self.dispatch_hook,
624 available_verbs: Arc::new(available_verbs),
625 disabled_verbs: Arc::new(disabled_verbs),
626 handler_by_name: Arc::new(handler_by_name),
627 degrade_safe_verbs: Arc::new(degrade_safe_verbs),
628 read_replay_safe_verbs: Arc::new(read_replay_safe_verbs),
629 reference_ring: Arc::new(crate::reference_ring::ReferenceRing::new()),
630 audit_batch,
631 })
632 }
633}
634
635fn collect_note_property_policies(
637 packs: &[Box<dyn PackRuntime>],
638) -> Result<Vec<NotePropertyPolicySpec>, RuntimeError> {
639 let mut policies = Vec::new();
640 let mut seen = HashSet::new();
641 for pack in packs {
642 let owned_kinds = pack.note_kinds();
643 for spec in pack.note_property_policies() {
644 if !owned_kinds.contains(&spec.kind) {
645 return Err(RuntimeError::InvalidInput(format!(
646 "pack {:?} declares property policy for unowned note kind {:?}, key {:?}",
647 pack.name(),
648 spec.kind,
649 spec.key
650 )));
651 }
652 if !seen.insert((spec.kind, spec.key)) {
653 return Err(RuntimeError::InvalidInput(format!(
654 "pack {:?} declares duplicate property policy for note kind {:?}, key {:?}",
655 pack.name(),
656 spec.kind,
657 spec.key
658 )));
659 }
660 policies.push(*spec);
661 }
662 }
663 Ok(policies)
664}
665
666fn validate_unique_note_kinds(packs: &[Box<dyn PackRuntime>]) -> Result<(), RuntimeError> {
672 let mut seen: HashMap<&str, &str> = HashMap::new();
673 for pack in packs {
674 for &kind in pack.note_kinds() {
675 if let Some(first_pack) = seen.insert(kind, pack.name()) {
676 return Err(RuntimeError::InvalidInput(format!(
677 "duplicate note kind {kind:?}: claimed by both {first_pack:?} and {:?}",
678 pack.name()
679 )));
680 }
681 }
682 }
683 Ok(())
684}
685
686fn validate_brain_consumer_kinds(packs: &[Box<dyn PackRuntime>]) -> Result<(), RuntimeError> {
693 for pack in packs {
694 for &kind in pack.brain_consumer_kinds() {
695 if kind == "*" || kind.trim().is_empty() || kind.trim() != kind {
696 return Err(RuntimeError::InvalidInput(format!(
697 "pack {:?} declares invalid brain consumer kind {kind:?}; declarations must be non-empty exact wire values and must not use the registry-owned \"*\" wildcard",
698 pack.name()
699 )));
700 }
701 }
702 }
703 Ok(())
704}
705
706fn validate_unique_verb_names(packs: &[Box<dyn PackRuntime>]) -> Result<(), RuntimeError> {
713 let mut seen: HashMap<&str, &str> = HashMap::new();
714 for pack in packs {
715 for handler in pack.handlers() {
716 if !matches!(handler.visibility, Visibility::Verb) {
717 continue;
718 }
719 if let Some(first_pack) = seen.insert(handler.name, pack.name()) {
720 return Err(RuntimeError::VerbCollision {
721 verb: handler.name.to_string(),
722 first_pack: first_pack.to_string(),
723 second_pack: pack.name().to_string(),
724 });
725 }
726 }
727 }
728 Ok(())
729}
730
731fn validate_unique_entity_types(packs: &[Box<dyn PackRuntime>]) -> Result<(), RuntimeError> {
741 let owned_defs = packs
742 .iter()
743 .flat_map(|p| p.entity_types().iter().map(move |def| (p.name(), def)));
744 khive_types::EntityTypeRegistry::check_extra_collisions(owned_defs)
745 .map_err(RuntimeError::InvalidInput)
746}
747
748fn validate_entity_type_note_kind_collisions(
752 packs: &[Box<dyn PackRuntime>],
753) -> Result<(), RuntimeError> {
754 let mut note_kinds = HashMap::new();
755 for pack in packs {
756 for &kind in pack.note_kinds() {
757 note_kinds
758 .entry(khive_types::to_snake_case(kind))
759 .or_insert(pack.name());
760 }
761 }
762
763 let check_definition = |definition: &EntityTypeDef, owner: &str| {
764 for name in std::iter::once(definition.type_name).chain(definition.aliases.iter().copied())
765 {
766 let normalized = khive_types::to_snake_case(name);
767 if let Some(note_owner) = note_kinds.get(&normalized) {
768 return Err(RuntimeError::InvalidInput(format!(
769 "entity subtype {name:?} from {owner:?} collides with note kind {normalized:?} from pack {note_owner:?}"
770 )));
771 }
772 }
773 Ok(())
774 };
775
776 let builtin = khive_types::EntityTypeRegistry::builtin();
777 for definition in builtin.definitions() {
778 check_definition(definition, "builtin")?;
779 }
780 for pack in packs {
781 for definition in pack.entity_types() {
782 check_definition(definition, pack.name())?;
783 }
784 }
785 Ok(())
786}
787
788fn find_pack_dependency_cycle(
789 packs: &[Box<dyn PackRuntime>],
790 name_to_idx: &HashMap<&str, usize>,
791 cycle_nodes: &HashSet<usize>,
792) -> Vec<String> {
793 fn visit(
794 idx: usize,
795 packs: &[Box<dyn PackRuntime>],
796 name_to_idx: &HashMap<&str, usize>,
797 cycle_nodes: &HashSet<usize>,
798 visiting: &mut Vec<usize>,
799 visited: &mut HashSet<usize>,
800 ) -> Option<Vec<String>> {
801 if let Some(pos) = visiting.iter().position(|&seen| seen == idx) {
802 let mut cycle: Vec<String> = visiting[pos..]
803 .iter()
804 .map(|&i| packs[i].name().to_string())
805 .collect();
806 cycle.push(packs[idx].name().to_string());
807 return Some(cycle);
808 }
809 if !visited.insert(idx) {
810 return None;
811 }
812 visiting.push(idx);
813 for &req in packs[idx].requires() {
814 let Some(&dep_idx) = name_to_idx.get(req) else {
815 continue;
816 };
817 if cycle_nodes.contains(&dep_idx) {
818 if let Some(cycle) =
819 visit(dep_idx, packs, name_to_idx, cycle_nodes, visiting, visited)
820 {
821 return Some(cycle);
822 }
823 }
824 }
825 visiting.pop();
826 None
827 }
828
829 let mut visited = HashSet::new();
830 for &idx in cycle_nodes {
831 let mut visiting = Vec::new();
832 if let Some(cycle) = visit(
833 idx,
834 packs,
835 name_to_idx,
836 cycle_nodes,
837 &mut visiting,
838 &mut visited,
839 ) {
840 return cycle;
841 }
842 }
843 cycle_nodes
844 .iter()
845 .map(|&idx| packs[idx].name().to_string())
846 .collect()
847}
848
849impl Default for VerbRegistryBuilder {
850 fn default() -> Self {
851 Self::new()
852 }
853}
854
855pub struct PackMetadataRegistry {
863 pub(super) registry: VerbRegistry,
864}
865
866impl PackMetadataRegistry {
867 pub fn has_verb(&self, verb: &str) -> bool {
868 self.registry.has_verb(verb)
869 }
870
871 pub fn describe_verb(&self, verb: &str) -> Result<Value, RuntimeError> {
872 self.registry.describe_verb(verb)
873 }
874
875 pub fn all_handlers_with_names(&self) -> Vec<(&str, &'static HandlerDef)> {
876 self.registry.all_handlers_with_names()
877 }
878
879 pub fn all_verbs(&self) -> Vec<&'static HandlerDef> {
880 self.registry.all_verbs()
881 }
882
883 pub fn pack_names(&self) -> Vec<&str> {
884 self.registry.pack_names()
885 }
886
887 pub fn pack_version(&self, name: &str) -> Option<&'static str> {
889 self.registry.pack_version(name)
890 }
891
892 pub fn pack_requires(&self, name: &str) -> Option<&'static [&'static str]> {
893 self.registry.pack_requires(name)
894 }
895
896 pub fn pack_note_kinds(&self, name: &str) -> Option<&'static [&'static str]> {
897 self.registry.pack_note_kinds(name)
898 }
899
900 pub fn pack_entity_kinds(&self, name: &str) -> Option<&'static [&'static str]> {
901 self.registry.pack_entity_kinds(name)
902 }
903
904 pub fn pack_verbs(&self, name: &str) -> Option<&'static [HandlerDef]> {
905 self.registry.pack_verbs(name)
906 }
907
908 pub fn all_entity_kinds(&self) -> Vec<&'static str> {
909 self.registry.all_entity_kinds()
910 }
911
912 pub fn all_note_kinds(&self) -> Vec<&'static str> {
913 self.registry.all_note_kinds()
914 }
915
916 pub fn all_edge_rules(&self) -> Vec<EdgeEndpointRule> {
917 self.registry.all_edge_rules()
918 }
919
920 pub fn all_edge_rules_with_packs(&self) -> Vec<(&str, EdgeEndpointRule)> {
922 self.registry.all_edge_rules_with_packs()
923 }
924}