1use serde::{Deserialize, Serialize};
11
12use crate::context::ContextKey;
13use crate::types::{
14 ActorId, ApprovalId, ArtifactId, ContentHash, FactId, GateId, ObservationId, ProposalId,
15 SpanId, Timestamp, TraceId, TraceReference, TraceSystemId, UnitInterval, ValidationCheckId,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum FactActorKind {
21 Human,
23 Suggestor,
25 System,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct FactActor {
32 id: ActorId,
33 kind: FactActorKind,
34}
35
36impl FactActor {
37 #[must_use]
39 pub fn id(&self) -> &ActorId {
40 &self.id
41 }
42
43 #[must_use]
45 pub fn kind(&self) -> FactActorKind {
46 self.kind
47 }
48
49 #[doc(hidden)]
50 pub fn new_projection(id: impl Into<ActorId>, kind: FactActorKind) -> Self {
51 Self {
52 id: id.into(),
53 kind,
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
60pub struct FactValidationSummary {
61 checks_passed: Vec<ValidationCheckId>,
62 checks_skipped: Vec<ValidationCheckId>,
63 warnings: Vec<String>,
64}
65
66impl FactValidationSummary {
67 #[must_use]
69 pub fn checks_passed(&self) -> &[ValidationCheckId] {
70 &self.checks_passed
71 }
72
73 #[must_use]
75 pub fn checks_skipped(&self) -> &[ValidationCheckId] {
76 &self.checks_skipped
77 }
78
79 #[must_use]
81 pub fn warnings(&self) -> &[String] {
82 &self.warnings
83 }
84
85 #[doc(hidden)]
86 pub fn new_projection(
87 checks_passed: Vec<ValidationCheckId>,
88 checks_skipped: Vec<ValidationCheckId>,
89 warnings: Vec<String>,
90 ) -> Self {
91 Self {
92 checks_passed,
93 checks_skipped,
94 warnings,
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(tag = "type", content = "id")]
102pub enum FactEvidenceRef {
103 Observation(ObservationId),
105 HumanApproval(ApprovalId),
107 Derived(ArtifactId),
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct FactLocalTrace {
114 trace_id: TraceId,
115 span_id: SpanId,
116 parent_span_id: Option<SpanId>,
117 sampled: bool,
118}
119
120impl FactLocalTrace {
121 #[must_use]
123 pub fn trace_id(&self) -> &TraceId {
124 &self.trace_id
125 }
126
127 #[must_use]
129 pub fn span_id(&self) -> &SpanId {
130 &self.span_id
131 }
132
133 #[must_use]
135 pub fn parent_span_id(&self) -> Option<&SpanId> {
136 self.parent_span_id.as_ref()
137 }
138
139 #[must_use]
141 pub fn sampled(&self) -> bool {
142 self.sampled
143 }
144
145 #[doc(hidden)]
146 pub fn new_projection(
147 trace_id: impl Into<TraceId>,
148 span_id: impl Into<SpanId>,
149 parent_span_id: Option<SpanId>,
150 sampled: bool,
151 ) -> Self {
152 Self {
153 trace_id: trace_id.into(),
154 span_id: span_id.into(),
155 parent_span_id,
156 sampled,
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct FactRemoteTrace {
164 system: TraceSystemId,
165 reference: TraceReference,
166 retrieval_auth: Option<String>,
167 retention_hint: Option<String>,
168}
169
170impl FactRemoteTrace {
171 #[must_use]
173 pub fn system(&self) -> &TraceSystemId {
174 &self.system
175 }
176
177 #[must_use]
179 pub fn reference(&self) -> &TraceReference {
180 &self.reference
181 }
182
183 #[must_use]
185 pub fn retrieval_auth(&self) -> Option<&str> {
186 self.retrieval_auth.as_deref()
187 }
188
189 #[must_use]
191 pub fn retention_hint(&self) -> Option<&str> {
192 self.retention_hint.as_deref()
193 }
194
195 #[doc(hidden)]
196 pub fn new_projection(
197 system: impl Into<TraceSystemId>,
198 reference: impl Into<TraceReference>,
199 retrieval_auth: Option<String>,
200 retention_hint: Option<String>,
201 ) -> Self {
202 Self {
203 system: system.into(),
204 reference: reference.into(),
205 retrieval_auth,
206 retention_hint,
207 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213#[serde(tag = "type")]
214pub enum FactTraceLink {
215 Local(FactLocalTrace),
217 Remote(FactRemoteTrace),
219}
220
221impl FactTraceLink {
222 #[must_use]
224 pub fn is_replay_eligible(&self) -> bool {
225 matches!(self, Self::Local(_))
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct FactPromotionRecord {
232 gate_id: GateId,
233 policy_version_hash: ContentHash,
234 approver: FactActor,
235 validation_summary: FactValidationSummary,
236 evidence_refs: Vec<FactEvidenceRef>,
237 trace_link: FactTraceLink,
238 promoted_at: Timestamp,
239}
240
241impl FactPromotionRecord {
242 #[must_use]
244 pub fn gate_id(&self) -> &GateId {
245 &self.gate_id
246 }
247
248 #[must_use]
250 pub fn policy_version_hash(&self) -> &ContentHash {
251 &self.policy_version_hash
252 }
253
254 #[must_use]
256 pub fn approver(&self) -> &FactActor {
257 &self.approver
258 }
259
260 #[must_use]
262 pub fn validation_summary(&self) -> &FactValidationSummary {
263 &self.validation_summary
264 }
265
266 #[must_use]
268 pub fn evidence_refs(&self) -> &[FactEvidenceRef] {
269 &self.evidence_refs
270 }
271
272 #[must_use]
274 pub fn trace_link(&self) -> &FactTraceLink {
275 &self.trace_link
276 }
277
278 #[must_use]
280 pub fn promoted_at(&self) -> &Timestamp {
281 &self.promoted_at
282 }
283
284 #[must_use]
286 pub fn is_replay_eligible(&self) -> bool {
287 self.trace_link.is_replay_eligible()
288 }
289
290 #[doc(hidden)]
291 pub fn new_projection(
292 gate_id: impl Into<GateId>,
293 policy_version_hash: ContentHash,
294 approver: FactActor,
295 validation_summary: FactValidationSummary,
296 evidence_refs: Vec<FactEvidenceRef>,
297 trace_link: FactTraceLink,
298 promoted_at: impl Into<Timestamp>,
299 ) -> Self {
300 Self {
301 gate_id: gate_id.into(),
302 policy_version_hash,
303 approver,
304 validation_summary,
305 evidence_refs,
306 trace_link,
307 promoted_at: promoted_at.into(),
308 }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
319pub struct ContextFact {
320 key: ContextKey,
322 id: FactId,
324 content: String,
326 promotion_record: FactPromotionRecord,
328 created_at: Timestamp,
330}
331
332impl ContextFact {
333 #[must_use]
339 pub fn new_projection(
340 key: ContextKey,
341 id: impl Into<FactId>,
342 content: impl Into<String>,
343 promotion_record: FactPromotionRecord,
344 created_at: impl Into<Timestamp>,
345 ) -> Self {
346 Self {
347 key,
348 id: id.into(),
349 content: content.into(),
350 promotion_record,
351 created_at: created_at.into(),
352 }
353 }
354
355 #[must_use]
357 pub fn key(&self) -> ContextKey {
358 self.key
359 }
360
361 #[must_use]
363 pub fn id(&self) -> &FactId {
364 &self.id
365 }
366
367 #[must_use]
369 pub fn content(&self) -> &str {
370 &self.content
371 }
372
373 #[must_use]
375 pub fn promotion_record(&self) -> &FactPromotionRecord {
376 &self.promotion_record
377 }
378
379 #[must_use]
381 pub fn created_at(&self) -> &Timestamp {
382 &self.created_at
383 }
384
385 #[must_use]
387 pub fn is_replay_eligible(&self) -> bool {
388 self.promotion_record.is_replay_eligible()
389 }
390
391 pub fn parse_json_content<T: serde::de::DeserializeOwned>(&self) -> serde_json::Result<T> {
397 serde_json::from_str(&self.content)
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
406pub struct ProposedFact {
407 pub key: ContextKey,
409 pub id: ProposalId,
411 pub content: String,
413 confidence: UnitInterval,
415 pub provenance: String,
417}
418
419impl ProposedFact {
420 #[must_use]
424 pub fn new(
425 key: ContextKey,
426 id: impl Into<ProposalId>,
427 content: impl Into<String>,
428 provenance: impl Into<String>,
429 ) -> Self {
430 Self {
431 key,
432 id: id.into(),
433 content: content.into(),
434 confidence: UnitInterval::ONE,
435 provenance: provenance.into(),
436 }
437 }
438
439 #[must_use]
441 pub fn key(&self) -> ContextKey {
442 self.key
443 }
444
445 #[must_use]
447 pub fn id(&self) -> &ProposalId {
448 &self.id
449 }
450
451 #[must_use]
453 pub fn content(&self) -> &str {
454 &self.content
455 }
456
457 #[must_use]
459 pub fn provenance(&self) -> &str {
460 &self.provenance
461 }
462
463 #[must_use]
465 pub fn confidence(&self) -> f64 {
466 self.confidence.as_f64()
467 }
468
469 #[must_use]
477 pub fn with_confidence(mut self, confidence: f64) -> Self {
478 self.confidence = UnitInterval::clamped(confidence);
479 self
480 }
481
482 pub fn parse_json_content<T: serde::de::DeserializeOwned>(&self) -> serde_json::Result<T> {
488 serde_json::from_str(&self.content)
489 }
490
491 pub fn from_json_payload<T: serde::Serialize>(
501 key: ContextKey,
502 id: impl Into<ProposalId>,
503 payload: &T,
504 provenance: impl Into<String>,
505 ) -> serde_json::Result<Self> {
506 Ok(Self::new(
507 key,
508 id,
509 serde_json::to_string(payload)?,
510 provenance,
511 ))
512 }
513
514 #[must_use]
532 pub fn adjust_confidence(mut self, delta: f64) -> Self {
533 self.confidence = self.confidence.saturating_add(delta);
534 self
535 }
536}
537
538pub const CONFIDENCE_STEP_TINY: f64 = 0.05;
540
541pub const CONFIDENCE_STEP_MINOR: f64 = 0.1;
543
544pub const CONFIDENCE_STEP_MEDIUM: f64 = 0.15;
546
547pub const CONFIDENCE_STEP_MAJOR: f64 = 0.2;
549
550pub const CONFIDENCE_STEP_PRIMARY: f64 = 0.25;
552
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
555pub struct ValidationError {
556 pub reason: String,
558}
559
560impl std::fmt::Display for ValidationError {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 write!(f, "validation failed: {}", self.reason)
563 }
564}
565
566impl std::error::Error for ValidationError {}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571
572 fn projection_record() -> FactPromotionRecord {
573 FactPromotionRecord::new_projection(
574 "projection-test",
575 ContentHash::from_hex(
576 "1111111111111111111111111111111111111111111111111111111111111111",
577 ),
578 FactActor::new_projection("actor-1", FactActorKind::System),
579 FactValidationSummary::default(),
580 Vec::new(),
581 FactTraceLink::Local(FactLocalTrace::new_projection(
582 "trace-1", "span-1", None, true,
583 )),
584 Timestamp::epoch(),
585 )
586 }
587
588 fn projection_fact(
589 key: ContextKey,
590 id: impl Into<FactId>,
591 content: impl Into<String>,
592 ) -> ContextFact {
593 ContextFact::new_projection(key, id, content, projection_record(), Timestamp::epoch())
594 }
595
596 #[test]
597 fn trace_link_local_is_replay_eligible() {
598 let local = FactTraceLink::Local(FactLocalTrace {
599 trace_id: "t1".into(),
600 span_id: "s1".into(),
601 parent_span_id: None,
602 sampled: true,
603 });
604 assert!(local.is_replay_eligible());
605 }
606
607 #[test]
608 fn trace_link_remote_is_not_replay_eligible() {
609 let remote = FactTraceLink::Remote(FactRemoteTrace {
610 system: "datadog".into(),
611 reference: "ref-1".into(),
612 retrieval_auth: None,
613 retention_hint: None,
614 });
615 assert!(!remote.is_replay_eligible());
616 }
617
618 #[test]
619 fn promotion_record_delegates_replay_eligibility() {
620 let local_record = FactPromotionRecord::new_projection(
621 "gate-1",
622 ContentHash::from_hex(
623 "1111111111111111111111111111111111111111111111111111111111111111",
624 ),
625 FactActor::new_projection("actor-1", FactActorKind::Human),
626 FactValidationSummary::default(),
627 Vec::new(),
628 FactTraceLink::Local(FactLocalTrace::new_projection("t1", "s1", None, true)),
629 "2026-01-01T00:00:00Z",
630 );
631 assert!(local_record.is_replay_eligible());
632
633 let remote_record = FactPromotionRecord::new_projection(
634 "gate-2",
635 ContentHash::from_hex(
636 "2222222222222222222222222222222222222222222222222222222222222222",
637 ),
638 FactActor::new_projection("actor-2", FactActorKind::System),
639 FactValidationSummary::default(),
640 Vec::new(),
641 FactTraceLink::Remote(FactRemoteTrace::new_projection("dd", "ref-1", None, None)),
642 "2026-01-01T00:00:00Z",
643 );
644 assert!(!remote_record.is_replay_eligible());
645 }
646
647 #[test]
648 fn fact_delegates_replay_eligibility() {
649 let fact = projection_fact(ContextKey::Seeds, "f1", "content");
650 assert!(fact.is_replay_eligible());
651 }
652
653 #[test]
654 fn proposed_fact_new_sets_fields() {
655 let pf = ProposedFact::new(ContextKey::Hypotheses, "p1", "my content", "gpt-4");
656 assert_eq!(pf.key, ContextKey::Hypotheses);
657 assert_eq!(pf.id, "p1");
658 assert_eq!(pf.content, "my content");
659 assert_eq!(pf.confidence(), 1.0);
660 assert_eq!(pf.provenance, "gpt-4");
661 }
662
663 #[test]
664 fn proposed_fact_with_confidence() {
665 let pf = ProposedFact::new(ContextKey::Signals, "p2", "c", "prov").with_confidence(0.42);
666 assert!((pf.confidence() - 0.42).abs() < f64::EPSILON);
667 }
668
669 #[test]
670 fn adjust_confidence_accumulates() {
671 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x")
672 .with_confidence(0.5)
673 .adjust_confidence(CONFIDENCE_STEP_MINOR)
674 .adjust_confidence(CONFIDENCE_STEP_MAJOR);
675 assert!((pf.confidence() - 0.8).abs() < f64::EPSILON);
676 }
677
678 #[test]
679 fn adjust_confidence_clamps_at_one() {
680 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x")
681 .with_confidence(0.9)
682 .adjust_confidence(CONFIDENCE_STEP_MAJOR);
683 assert_eq!(pf.confidence(), 1.0);
684 }
685
686 #[test]
687 fn adjust_confidence_clamps_at_zero() {
688 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x")
689 .with_confidence(0.1)
690 .adjust_confidence(-0.5);
691 assert_eq!(pf.confidence(), 0.0);
692 }
693
694 #[test]
695 fn with_confidence_clamps_high() {
696 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x").with_confidence(1.5);
697 assert_eq!(pf.confidence(), 1.0);
698 }
699
700 #[test]
701 fn with_confidence_clamps_negative() {
702 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x").with_confidence(-0.1);
703 assert_eq!(pf.confidence(), 0.0);
704 }
705
706 #[test]
707 fn with_confidence_normalizes_nan() {
708 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x").with_confidence(f64::NAN);
709 assert_eq!(pf.confidence(), 0.0);
710 }
711
712 #[test]
713 fn with_confidence_normalizes_infinity() {
714 let pf = ProposedFact::new(ContextKey::Seeds, "p", "c", "x").with_confidence(f64::INFINITY);
715 assert_eq!(pf.confidence(), 0.0);
716 }
717
718 #[test]
719 fn proposed_fact_deserialization_rejects_out_of_range_confidence() {
720 let json = r#"{
721 "key":"Seeds",
722 "id":"p",
723 "content":"c",
724 "confidence":1.5,
725 "provenance":"test"
726 }"#;
727 let result = serde_json::from_str::<ProposedFact>(json);
728 assert!(result.is_err());
729 }
730
731 #[test]
732 fn proposed_fact_parse_json_content_succeeds_for_valid_json() {
733 #[derive(serde::Deserialize, PartialEq, Debug)]
734 struct Payload {
735 kind: String,
736 score: f64,
737 }
738 let pf = ProposedFact::new(
739 ContextKey::Hypotheses,
740 "p",
741 r#"{"kind":"vote","score":0.7}"#,
742 "test",
743 );
744 let parsed: Payload = pf.parse_json_content().unwrap();
745 assert_eq!(
746 parsed,
747 Payload {
748 kind: "vote".into(),
749 score: 0.7,
750 }
751 );
752 }
753
754 #[test]
755 fn proposed_fact_parse_json_content_returns_error_for_invalid_json() {
756 let pf = ProposedFact::new(ContextKey::Hypotheses, "p", "not json", "test");
757 let parsed = pf.parse_json_content::<serde_json::Value>();
758 assert!(parsed.is_err());
759 }
760
761 #[test]
762 fn fact_parse_json_content_succeeds_for_valid_json() {
763 #[derive(serde::Deserialize, PartialEq, Debug)]
764 struct Payload {
765 label: String,
766 }
767 let fact = projection_fact(ContextKey::Seeds, "f", r#"{"label":"x"}"#);
768 let parsed: Payload = fact.parse_json_content().unwrap();
769 assert_eq!(parsed, Payload { label: "x".into() });
770 }
771
772 #[test]
773 fn proposed_fact_from_json_payload_round_trips() {
774 #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
775 struct Payload {
776 kind: String,
777 score: f64,
778 }
779 let payload = Payload {
780 kind: "vote".into(),
781 score: 0.7,
782 };
783 let pf =
784 ProposedFact::from_json_payload(ContextKey::Hypotheses, "p", &payload, "test").unwrap();
785 assert_eq!(pf.key, ContextKey::Hypotheses);
786 assert_eq!(pf.id, "p");
787 assert_eq!(pf.provenance, "test");
788 let parsed: Payload = pf.parse_json_content().unwrap();
789 assert_eq!(parsed, payload);
790 }
791
792 #[test]
793 fn proposed_fact_from_json_payload_propagates_serialization_error() {
794 use std::collections::HashMap;
795 let mut map: HashMap<Vec<u8>, &str> = HashMap::new();
796 map.insert(vec![1, 2, 3], "value");
797 let result = ProposedFact::from_json_payload(ContextKey::Hypotheses, "p", &map, "test");
798 assert!(result.is_err());
799 }
800
801 #[test]
802 fn fact_projection_json_payload_round_trips() {
803 #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
804 struct Payload {
805 label: String,
806 }
807 let payload = Payload { label: "x".into() };
808 let fact = projection_fact(
809 ContextKey::Seeds,
810 "f",
811 serde_json::to_string(&payload).unwrap(),
812 );
813 let parsed: Payload = fact.parse_json_content().unwrap();
814 assert_eq!(parsed, payload);
815 }
816
817 #[test]
818 fn validation_error_display() {
819 let err = ValidationError {
820 reason: "bad input".into(),
821 };
822 assert_eq!(err.to_string(), "validation failed: bad input");
823 }
824
825 #[test]
826 fn validation_error_is_std_error() {
827 let err = ValidationError {
828 reason: "test".into(),
829 };
830 let _: &dyn std::error::Error = &err;
831 }
832
833 #[test]
834 fn fact_accessors() {
835 let fact = projection_fact(ContextKey::Constraints, "f2", "body");
836 assert_eq!(fact.key(), ContextKey::Constraints);
837 assert_eq!(fact.id(), "f2");
838 assert_eq!(fact.content(), "body");
839 assert_eq!(fact.created_at(), "1970-01-01T00:00:00Z");
840 assert_eq!(fact.promotion_record().gate_id(), "projection-test");
841 }
842
843 #[test]
844 fn fact_actor_accessors() {
845 let actor = FactActor::new_projection("agent-x", FactActorKind::Suggestor);
846 assert_eq!(actor.id(), "agent-x");
847 assert_eq!(actor.kind(), FactActorKind::Suggestor);
848 }
849
850 #[test]
851 fn validation_summary_accessors() {
852 let vs = FactValidationSummary::new_projection(
853 vec!["check-a".into()],
854 vec!["check-b".into()],
855 vec!["warn-c".into()],
856 );
857 assert_eq!(vs.checks_passed(), &["check-a"]);
858 assert_eq!(vs.checks_skipped(), &["check-b"]);
859 assert_eq!(vs.warnings(), &["warn-c"]);
860 }
861
862 #[test]
863 fn local_trace_accessors() {
864 let lt =
865 FactLocalTrace::new_projection("trace-1", "span-1", Some("parent-1".into()), false);
866 assert_eq!(lt.trace_id(), "trace-1");
867 assert_eq!(lt.span_id(), "span-1");
868 assert_eq!(lt.parent_span_id().map(SpanId::as_str), Some("parent-1"));
869 assert!(!lt.sampled());
870 }
871
872 #[test]
873 fn remote_trace_accessors() {
874 let rt =
875 FactRemoteTrace::new_projection("sys", "ref", Some("auth".into()), Some("30d".into()));
876 assert_eq!(rt.system(), "sys");
877 assert_eq!(rt.reference(), "ref");
878 assert_eq!(rt.retrieval_auth(), Some("auth"));
879 assert_eq!(rt.retention_hint(), Some("30d"));
880 }
881
882 mod prop {
883 use super::*;
884 use proptest::prelude::*;
885
886 fn arb_context_key() -> impl Strategy<Value = ContextKey> {
887 prop_oneof![
888 Just(ContextKey::Seeds),
889 Just(ContextKey::Hypotheses),
890 Just(ContextKey::Strategies),
891 Just(ContextKey::Constraints),
892 Just(ContextKey::Signals),
893 Just(ContextKey::Competitors),
894 Just(ContextKey::Evaluations),
895 Just(ContextKey::Proposals),
896 Just(ContextKey::Diagnostic),
897 Just(ContextKey::Votes),
898 Just(ContextKey::Disagreements),
899 Just(ContextKey::ConsensusOutcomes),
900 ]
901 }
902
903 proptest! {
904 #[test]
905 fn proposed_fact_always_constructible(
906 key in arb_context_key(),
907 id in "[a-z]{1,20}",
908 content in ".*",
909 prov in "[a-z0-9-]{1,30}",
910 ) {
911 let pf = ProposedFact::new(key, id.clone(), content.clone(), prov.clone());
912 prop_assert_eq!(pf.key, key);
913 prop_assert_eq!(&pf.id, &id);
914 prop_assert_eq!(&pf.content, &content);
915 prop_assert_eq!(&pf.provenance, &prov);
916 prop_assert!((pf.confidence() - 1.0).abs() < f64::EPSILON);
917 }
918 }
919 }
920}