Skip to main content

ahri_tre_protocol/
model.rs

1use crate::{
2    ProtocolVersion, PublicName,
3    domain::DomainSelector,
4    pagination::PageRequest,
5    public_error::{ProtocolError, ValidationIssue, ValidationIssueCode},
6    refs::{DomainRef, EntityRef, RelationRef, SessionSelector, TagSummary},
7    search::{PredicateSet, SearchCursorBinding, TagPredicate, TextPredicate},
8};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(tag = "kind", rename_all = "snake_case")]
13pub enum EntitySelector {
14    Id {
15        entity: EntityRef,
16        #[serde(default, skip_serializing_if = "Option::is_none")]
17        domain: Option<DomainSelector>,
18    },
19    Name {
20        domain: DomainSelector,
21        name: PublicName,
22    },
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(tag = "kind", rename_all = "snake_case")]
27pub enum RelationSelector {
28    Id {
29        relation: RelationRef,
30        #[serde(default, skip_serializing_if = "Option::is_none")]
31        domain: Option<DomainSelector>,
32    },
33    Name {
34        domain: DomainSelector,
35        name: PublicName,
36    },
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct EntitySummary {
41    pub entity: EntityRef,
42    pub domain: DomainRef,
43    pub name: String,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub description: Option<String>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub ontology_class: Option<String>,
48    #[serde(default)]
49    pub tags: Vec<TagSummary>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct EntityDetail {
54    pub summary: EntitySummary,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub agent_instructions: Option<String>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub ontology_namespace: Option<String>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct RelationDefinitionSummary {
63    pub relation: RelationRef,
64    pub domain: DomainRef,
65    pub name: String,
66    pub subject_entity: EntityRef,
67    pub object_entity: EntityRef,
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub subject_entity_name: Option<String>,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub object_entity_name: Option<String>,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub description: Option<String>,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub ontology_class: Option<String>,
76    #[serde(default)]
77    pub tags: Vec<TagSummary>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct RelationDefinitionDetail {
82    pub summary: RelationDefinitionSummary,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub agent_instructions: Option<String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub ontology_namespace: Option<String>,
87}
88
89pub type EntityInstanceSummary = SemanticInstanceReadback;
90
91pub type RelationInstanceSummary = SemanticInstanceReadback;
92
93pub type ModelMappingSummary = SemanticMappingReadback;
94
95pub type ModelLinkSummary = SemanticLinkIdentity;
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct ModelWarnings {
99    #[serde(default, skip_serializing_if = "Vec::is_empty")]
100    pub warnings: Vec<String>,
101    #[serde(default, skip_serializing_if = "Vec::is_empty")]
102    pub validation_errors: Vec<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct ModelMutationIntent {
107    #[serde(default)]
108    pub force_update: bool,
109    #[serde(default)]
110    pub check_dependencies: bool,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct RelationSummary {
115    pub relation: RelationRef,
116    pub domain: DomainRef,
117    pub name: String,
118    pub source_entity: EntityRef,
119    pub source_entity_name: String,
120    pub target_entity: EntityRef,
121    pub target_entity_name: String,
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub description: Option<String>,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub ontology_class: Option<String>,
126    #[serde(default)]
127    pub tags: Vec<TagSummary>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct SearchEntitiesRequest {
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub session: Option<SessionSelector>,
134    pub predicates: PredicateSet<EntityPredicate>,
135    pub page: PageRequest,
136}
137
138/// Datastore-scoped semantic entity search for the browser service lane.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct BrowserEntitySearchRequest {
141    pub datastore_id: crate::PublicUuid,
142    pub search: SearchEntitiesRequest,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct EntitySearchCursorAnchor {
147    pub domain: DomainRef,
148    pub entity: EntityRef,
149    pub name: String,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct EntitySearchCursorQuery {
154    pub predicates: PredicateSet<EntityPredicate>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub struct EntitySearchCursor {
159    pub binding: SearchCursorBinding<EntitySearchCursorQuery>,
160    pub after: EntitySearchCursorAnchor,
161}
162
163/// Browser-safe response for a datastore-scoped model-entity search.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct BrowserEntitySearchResponse {
166    pub protocol_version: ProtocolVersion,
167    pub datastore_id: crate::PublicUuid,
168    pub family: String,
169    pub predicates: PredicateSet<EntityPredicate>,
170    #[serde(default)]
171    pub entities: Vec<crate::semantic_discovery::BrowserEntityDiscoveryRecord>,
172    pub returned_count: u16,
173    pub requested_limit: u16,
174    pub ordering: crate::study::BrowserSearchOrdering,
175    pub has_more: bool,
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub next_cursor: Option<String>,
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub empty_state: Option<BrowserEntitySearchEmptyState>,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183pub struct BrowserEntitySearchEmptyState {
184    pub code: String,
185    pub message: String,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189pub struct BrowserEntitySearchCursorQuery {
190    pub datastore_id: crate::PublicUuid,
191    pub family: String,
192    pub predicates: PredicateSet<EntityPredicate>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196pub struct BrowserEntitySearchCursor {
197    pub binding: SearchCursorBinding<BrowserEntitySearchCursorQuery>,
198    pub after: EntitySearchCursorAnchor,
199}
200
201#[allow(clippy::result_large_err)]
202impl SearchEntitiesRequest {
203    #[allow(clippy::result_large_err)]
204    pub fn validate_for_browser(
205        &mut self,
206        datastore_id: crate::PublicUuid,
207        protocol_version: &ProtocolVersion,
208    ) -> Result<(), ProtocolError> {
209        let mut issues = Vec::new();
210        collect_entity_predicate_set_issues("body.predicates", &self.predicates, &mut issues);
211        collect_entity_predicate_issues(&mut self.predicates, &mut issues);
212        if let Err(message) = self.page.validate() {
213            issues.push(ValidationIssue::new(
214                "body.page.limit",
215                ValidationIssueCode::OutOfRange,
216                message,
217            ));
218        }
219        if let Some(raw_cursor) = self.page.cursor.as_deref() {
220            match serde_json::from_str::<BrowserEntitySearchCursor>(raw_cursor) {
221                Ok(cursor) if cursor.binding == self.browser_cursor_binding(datastore_id, protocol_version) => {}
222                Ok(_) => issues.push(ValidationIssue::new("body.page.cursor", ValidationIssueCode::Conflict, "browser model-entity search cursor does not match the datastore, search family, predicates, page limit, or protocol version")),
223                Err(_) => issues.push(ValidationIssue::new("body.page.cursor", ValidationIssueCode::InvalidFormat, "browser model-entity search cursor is invalid")),
224            }
225        }
226        if issues.is_empty() {
227            Ok(())
228        } else {
229            Err(ProtocolError::validation(
230                "browser model-entity search request is invalid",
231                issues,
232            ))
233        }
234    }
235
236    #[allow(clippy::result_large_err)]
237    pub fn browser_decoded_cursor(
238        &self,
239        datastore_id: crate::PublicUuid,
240        protocol_version: &ProtocolVersion,
241    ) -> Result<Option<EntitySearchCursorAnchor>, ProtocolError> {
242        let Some(raw_cursor) = self.page.cursor.as_deref() else {
243            return Ok(None);
244        };
245        let cursor: BrowserEntitySearchCursor = serde_json::from_str(raw_cursor).map_err(|_| {
246            ProtocolError::validation(
247                "browser model-entity search request is invalid",
248                vec![ValidationIssue::new(
249                    "body.page.cursor",
250                    ValidationIssueCode::InvalidFormat,
251                    "browser model-entity search cursor is invalid",
252                )],
253            )
254        })?;
255        if cursor.binding != self.browser_cursor_binding(datastore_id, protocol_version) {
256            return Err(ProtocolError::validation(
257                "browser model-entity search request is invalid",
258                vec![ValidationIssue::new(
259                    "body.page.cursor",
260                    ValidationIssueCode::Conflict,
261                    "browser model-entity search cursor does not match the datastore, search family, predicates, page limit, or protocol version",
262                )],
263            ));
264        }
265        Ok(Some(cursor.after))
266    }
267
268    #[allow(clippy::result_large_err)]
269    pub fn encode_browser_cursor(
270        &self,
271        datastore_id: crate::PublicUuid,
272        protocol_version: &ProtocolVersion,
273        after: EntitySearchCursorAnchor,
274    ) -> Result<String, ProtocolError> {
275        serde_json::to_string(&BrowserEntitySearchCursor {
276            binding: self.browser_cursor_binding(datastore_id, protocol_version),
277            after,
278        })
279        .map_err(|error| {
280            ProtocolError::new(
281                crate::public_error::ProtocolErrorCode::Internal,
282                format!("failed to encode browser entity search cursor: {error}"),
283            )
284        })
285    }
286
287    fn browser_cursor_binding(
288        &self,
289        datastore_id: crate::PublicUuid,
290        protocol_version: &ProtocolVersion,
291    ) -> SearchCursorBinding<BrowserEntitySearchCursorQuery> {
292        SearchCursorBinding {
293            query: BrowserEntitySearchCursorQuery {
294                datastore_id,
295                family: "model_entities".to_string(),
296                predicates: self.predicates.clone(),
297            },
298            page_limit: self.page.limit,
299            protocol_version: protocol_version.to_string(),
300        }
301    }
302    pub fn validate(&mut self, protocol_version: &ProtocolVersion) -> Result<(), ProtocolError> {
303        let mut issues = Vec::new();
304        collect_entity_predicate_set_issues("body.predicates", &self.predicates, &mut issues);
305        collect_entity_predicate_issues(&mut self.predicates, &mut issues);
306        if let Err(message) = self.page.validate() {
307            issues.push(ValidationIssue::new(
308                "body.page.limit",
309                ValidationIssueCode::OutOfRange,
310                message,
311            ));
312        }
313        collect_entity_cursor_issues(self, protocol_version, &mut issues);
314
315        if issues.is_empty() {
316            Ok(())
317        } else {
318            Err(ProtocolError::validation(
319                "entity search request is invalid",
320                issues,
321            ))
322        }
323    }
324
325    pub fn decoded_cursor(
326        &self,
327        protocol_version: &ProtocolVersion,
328    ) -> Result<Option<EntitySearchCursorAnchor>, ProtocolError> {
329        match self.page.cursor.as_deref() {
330            None => Ok(None),
331            Some(raw_cursor) => {
332                let cursor: EntitySearchCursor =
333                    serde_json::from_str(raw_cursor).map_err(|error| {
334                        ProtocolError::validation(
335                            "entity search request is invalid",
336                            vec![ValidationIssue::new(
337                                "body.page.cursor",
338                                ValidationIssueCode::InvalidFormat,
339                                format!("entity search cursor is invalid: {error}"),
340                            )],
341                        )
342                    })?;
343                let expected = self.cursor_binding(protocol_version);
344                if cursor.binding != expected {
345                    return Err(ProtocolError::validation(
346                        "entity search request is invalid",
347                        vec![ValidationIssue::new(
348                            "body.page.cursor",
349                            ValidationIssueCode::Conflict,
350                            "entity search cursor does not match the request predicates, page limit, or protocol version",
351                        )],
352                    ));
353                }
354                Ok(Some(cursor.after))
355            }
356        }
357    }
358
359    pub fn encode_cursor(
360        &self,
361        protocol_version: &ProtocolVersion,
362        after: EntitySearchCursorAnchor,
363    ) -> Result<String, ProtocolError> {
364        serde_json::to_string(&EntitySearchCursor {
365            binding: self.cursor_binding(protocol_version),
366            after,
367        })
368        .map_err(|error| {
369            ProtocolError::new(
370                crate::public_error::ProtocolErrorCode::Internal,
371                format!("failed to encode entity search cursor: {error}"),
372            )
373        })
374    }
375
376    fn cursor_binding(
377        &self,
378        protocol_version: &ProtocolVersion,
379    ) -> SearchCursorBinding<EntitySearchCursorQuery> {
380        SearchCursorBinding {
381            query: EntitySearchCursorQuery {
382                predicates: self.predicates.clone(),
383            },
384            page_limit: self.page.limit,
385            protocol_version: protocol_version.to_string(),
386        }
387    }
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
391pub struct SearchRelationsRequest {
392    #[serde(default, skip_serializing_if = "Option::is_none")]
393    pub session: Option<SessionSelector>,
394    pub predicates: PredicateSet<RelationPredicate>,
395    pub page: PageRequest,
396}
397
398/// Datastore-scoped semantic relation search for the browser service lane.
399#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
400pub struct BrowserRelationSearchRequest {
401    pub datastore_id: crate::PublicUuid,
402    pub search: SearchRelationsRequest,
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
406pub struct RelationSearchCursorAnchor {
407    pub domain: DomainRef,
408    pub relation: RelationRef,
409    pub name: String,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
413pub struct RelationSearchCursorQuery {
414    pub predicates: PredicateSet<RelationPredicate>,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418pub struct RelationSearchCursor {
419    pub binding: SearchCursorBinding<RelationSearchCursorQuery>,
420    pub after: RelationSearchCursorAnchor,
421}
422
423/// Browser-safe response for a datastore-scoped model-relation search.
424#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
425pub struct BrowserRelationSearchResponse {
426    pub protocol_version: ProtocolVersion,
427    pub datastore_id: crate::PublicUuid,
428    pub family: String,
429    pub predicates: PredicateSet<RelationPredicate>,
430    #[serde(default)]
431    pub relations: Vec<crate::semantic_discovery::BrowserEntityRelationDiscoveryRecord>,
432    pub returned_count: u16,
433    pub requested_limit: u16,
434    pub ordering: crate::study::BrowserSearchOrdering,
435    pub has_more: bool,
436    #[serde(default, skip_serializing_if = "Option::is_none")]
437    pub next_cursor: Option<String>,
438    #[serde(default, skip_serializing_if = "Option::is_none")]
439    pub empty_state: Option<BrowserRelationSearchEmptyState>,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443pub struct BrowserRelationSearchEmptyState {
444    pub code: String,
445    pub message: String,
446}
447
448#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct BrowserRelationSearchCursorQuery {
450    pub datastore_id: crate::PublicUuid,
451    pub family: String,
452    pub predicates: PredicateSet<RelationPredicate>,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
456pub struct BrowserRelationSearchCursor {
457    pub binding: SearchCursorBinding<BrowserRelationSearchCursorQuery>,
458    pub after: RelationSearchCursorAnchor,
459}
460
461#[allow(clippy::result_large_err)]
462impl SearchRelationsRequest {
463    #[allow(clippy::result_large_err)]
464    pub fn validate_for_browser(
465        &mut self,
466        datastore_id: crate::PublicUuid,
467        protocol_version: &ProtocolVersion,
468    ) -> Result<(), ProtocolError> {
469        let mut issues = Vec::new();
470        collect_entity_predicate_set_issues("body.predicates", &self.predicates, &mut issues);
471        collect_relation_predicate_issues(&mut self.predicates, &mut issues);
472        if let Err(message) = self.page.validate() {
473            issues.push(ValidationIssue::new(
474                "body.page.limit",
475                ValidationIssueCode::OutOfRange,
476                message,
477            ));
478        }
479        if let Some(raw_cursor) = self.page.cursor.as_deref() {
480            match serde_json::from_str::<BrowserRelationSearchCursor>(raw_cursor) {
481                Ok(cursor)
482                    if cursor.binding
483                        == self.browser_cursor_binding(datastore_id, protocol_version) => {}
484                Ok(_) => issues.push(ValidationIssue::new(
485                    "body.page.cursor",
486                    ValidationIssueCode::Conflict,
487                    "browser model-relation search cursor does not match the datastore, search family, predicates, page limit, or protocol version",
488                )),
489                Err(_) => issues.push(ValidationIssue::new(
490                    "body.page.cursor",
491                    ValidationIssueCode::InvalidFormat,
492                    "browser model-relation search cursor is invalid",
493                )),
494            }
495        }
496        if issues.is_empty() {
497            Ok(())
498        } else {
499            Err(ProtocolError::validation(
500                "browser model-relation search request is invalid",
501                issues,
502            ))
503        }
504    }
505
506    #[allow(clippy::result_large_err)]
507    pub fn browser_decoded_cursor(
508        &self,
509        datastore_id: crate::PublicUuid,
510        protocol_version: &ProtocolVersion,
511    ) -> Result<Option<RelationSearchCursorAnchor>, ProtocolError> {
512        let Some(raw_cursor) = self.page.cursor.as_deref() else {
513            return Ok(None);
514        };
515        let cursor: BrowserRelationSearchCursor =
516            serde_json::from_str(raw_cursor).map_err(|_| {
517                ProtocolError::validation(
518                    "browser model-relation search request is invalid",
519                    vec![ValidationIssue::new(
520                        "body.page.cursor",
521                        ValidationIssueCode::InvalidFormat,
522                        "browser model-relation search cursor is invalid",
523                    )],
524                )
525            })?;
526        if cursor.binding != self.browser_cursor_binding(datastore_id, protocol_version) {
527            return Err(ProtocolError::validation(
528                "browser model-relation search request is invalid",
529                vec![ValidationIssue::new(
530                    "body.page.cursor",
531                    ValidationIssueCode::Conflict,
532                    "browser model-relation search cursor does not match the datastore, search family, predicates, page limit, or protocol version",
533                )],
534            ));
535        }
536        Ok(Some(cursor.after))
537    }
538
539    #[allow(clippy::result_large_err)]
540    pub fn encode_browser_cursor(
541        &self,
542        datastore_id: crate::PublicUuid,
543        protocol_version: &ProtocolVersion,
544        after: RelationSearchCursorAnchor,
545    ) -> Result<String, ProtocolError> {
546        serde_json::to_string(&BrowserRelationSearchCursor {
547            binding: self.browser_cursor_binding(datastore_id, protocol_version),
548            after,
549        })
550        .map_err(|error| {
551            ProtocolError::new(
552                crate::public_error::ProtocolErrorCode::Internal,
553                format!("failed to encode browser model-relation search cursor: {error}"),
554            )
555        })
556    }
557    pub fn validate(&mut self, protocol_version: &ProtocolVersion) -> Result<(), ProtocolError> {
558        let mut issues = Vec::new();
559        collect_entity_predicate_set_issues("body.predicates", &self.predicates, &mut issues);
560        collect_relation_predicate_issues(&mut self.predicates, &mut issues);
561        if let Err(message) = self.page.validate() {
562            issues.push(ValidationIssue::new(
563                "body.page.limit",
564                ValidationIssueCode::OutOfRange,
565                message,
566            ));
567        }
568        collect_relation_cursor_issues(self, protocol_version, &mut issues);
569
570        if issues.is_empty() {
571            Ok(())
572        } else {
573            Err(ProtocolError::validation(
574                "relation search request is invalid",
575                issues,
576            ))
577        }
578    }
579
580    pub fn decoded_cursor(
581        &self,
582        protocol_version: &ProtocolVersion,
583    ) -> Result<Option<RelationSearchCursorAnchor>, ProtocolError> {
584        match self.page.cursor.as_deref() {
585            None => Ok(None),
586            Some(raw_cursor) => {
587                let cursor: RelationSearchCursor =
588                    serde_json::from_str(raw_cursor).map_err(|error| {
589                        ProtocolError::validation(
590                            "relation search request is invalid",
591                            vec![ValidationIssue::new(
592                                "body.page.cursor",
593                                ValidationIssueCode::InvalidFormat,
594                                format!("relation search cursor is invalid: {error}"),
595                            )],
596                        )
597                    })?;
598                let expected = self.cursor_binding(protocol_version);
599                if cursor.binding != expected {
600                    return Err(ProtocolError::validation(
601                        "relation search request is invalid",
602                        vec![ValidationIssue::new(
603                            "body.page.cursor",
604                            ValidationIssueCode::Conflict,
605                            "relation search cursor does not match the request predicates, page limit, or protocol version",
606                        )],
607                    ));
608                }
609                Ok(Some(cursor.after))
610            }
611        }
612    }
613
614    pub fn encode_cursor(
615        &self,
616        protocol_version: &ProtocolVersion,
617        after: RelationSearchCursorAnchor,
618    ) -> Result<String, ProtocolError> {
619        serde_json::to_string(&RelationSearchCursor {
620            binding: self.cursor_binding(protocol_version),
621            after,
622        })
623        .map_err(|error| {
624            ProtocolError::new(
625                crate::public_error::ProtocolErrorCode::Internal,
626                format!("failed to encode relation search cursor: {error}"),
627            )
628        })
629    }
630
631    fn cursor_binding(
632        &self,
633        protocol_version: &ProtocolVersion,
634    ) -> SearchCursorBinding<RelationSearchCursorQuery> {
635        SearchCursorBinding {
636            query: RelationSearchCursorQuery {
637                predicates: self.predicates.clone(),
638            },
639            page_limit: self.page.limit,
640            protocol_version: protocol_version.to_string(),
641        }
642    }
643
644    fn browser_cursor_binding(
645        &self,
646        datastore_id: crate::PublicUuid,
647        protocol_version: &ProtocolVersion,
648    ) -> SearchCursorBinding<BrowserRelationSearchCursorQuery> {
649        SearchCursorBinding {
650            query: BrowserRelationSearchCursorQuery {
651                datastore_id,
652                family: "model_relations".to_string(),
653                predicates: self.predicates.clone(),
654            },
655            page_limit: self.page.limit,
656            protocol_version: protocol_version.to_string(),
657        }
658    }
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
662pub struct ListEntitiesRequest {
663    #[serde(default, skip_serializing_if = "Option::is_none")]
664    pub session: Option<SessionSelector>,
665    pub domain: DomainSelector,
666}
667
668#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
669pub struct GetEntityRequest {
670    #[serde(default, skip_serializing_if = "Option::is_none")]
671    pub session: Option<SessionSelector>,
672    pub entity: EntitySelector,
673}
674
675#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
676pub struct AddEntityRequest {
677    #[serde(default, skip_serializing_if = "Option::is_none")]
678    pub session: Option<SessionSelector>,
679    pub domain: DomainSelector,
680    pub name: PublicName,
681    #[serde(default, skip_serializing_if = "Option::is_none")]
682    pub description: Option<String>,
683    #[serde(default, skip_serializing_if = "Option::is_none")]
684    pub agent_instructions: Option<String>,
685    #[serde(default, skip_serializing_if = "Option::is_none")]
686    pub ontology_namespace: Option<String>,
687    #[serde(default, skip_serializing_if = "Option::is_none")]
688    pub ontology_class: Option<String>,
689    pub intent: ModelMutationIntent,
690}
691
692#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
693pub struct EntityListResponse {
694    pub entities: Vec<EntitySummary>,
695    #[serde(default, skip_serializing_if = "Option::is_none")]
696    pub next_cursor: Option<String>,
697    #[serde(default, skip_serializing_if = "Option::is_none")]
698    pub warnings: Option<ModelWarnings>,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
702pub struct EntityDetailResponse {
703    pub entity: EntityDetail,
704    #[serde(default, skip_serializing_if = "Option::is_none")]
705    pub warnings: Option<ModelWarnings>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
709pub struct EntityInstanceGetRequest {
710    #[serde(default, skip_serializing_if = "Option::is_none")]
711    pub session: Option<SessionSelector>,
712    pub instance_id: crate::refs::ObjectRef,
713}
714
715#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
716pub struct EntityInstanceListRequest {
717    #[serde(default, skip_serializing_if = "Option::is_none")]
718    pub session: Option<SessionSelector>,
719    pub entity: EntitySelector,
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
723pub struct EntityInstanceAddRequest {
724    #[serde(default, skip_serializing_if = "Option::is_none")]
725    pub session: Option<SessionSelector>,
726    pub entity: EntitySelector,
727    #[serde(default, skip_serializing_if = "Option::is_none")]
728    pub transformation_id: Option<crate::refs::ObjectRef>,
729    #[serde(default, skip_serializing_if = "Option::is_none")]
730    pub label: Option<String>,
731    #[serde(default, skip_serializing_if = "Option::is_none")]
732    pub note: Option<String>,
733    pub intent: ModelMutationIntent,
734}
735
736pub type EntityInstanceListResponse = SemanticInstanceListResponse;
737
738pub type EntityInstanceDetailResponse = SemanticInstanceDetailResponse;
739
740#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
741pub struct EntityInstanceMapAddRequest {
742    #[serde(default, skip_serializing_if = "Option::is_none")]
743    pub session: Option<SessionSelector>,
744    pub study: SemanticInput<crate::study::StudySelector>,
745    pub instance_id: crate::refs::ObjectRef,
746    pub external_id: String,
747    #[serde(default, skip_serializing_if = "Option::is_none")]
748    pub transformation_id: Option<crate::refs::ObjectRef>,
749    pub intent: ModelMutationIntent,
750}
751
752#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
753pub struct EntityInstanceMapGetRequest {
754    #[serde(default, skip_serializing_if = "Option::is_none")]
755    pub session: Option<SessionSelector>,
756    pub study: SemanticInput<crate::study::StudySelector>,
757    pub entity: EntitySelector,
758    pub external_id: String,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
762pub struct EntityInstanceMapListRequest {
763    #[serde(default, skip_serializing_if = "Option::is_none")]
764    pub session: Option<SessionSelector>,
765    pub study: SemanticInput<crate::study::StudySelector>,
766    #[serde(default, skip_serializing_if = "Option::is_none")]
767    pub entity: Option<EntitySelector>,
768}
769
770pub type EntityInstanceMapResponse = SemanticMappingDetailResponse;
771
772#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
773pub struct EntityInstanceAssetLinkAddRequest {
774    #[serde(default, skip_serializing_if = "Option::is_none")]
775    pub session: Option<SessionSelector>,
776    pub study: SemanticInput<crate::study::StudySelector>,
777    pub instance_id: crate::refs::ObjectRef,
778    pub asset: SemanticInput<crate::asset::AssetSelector>,
779    #[serde(default, skip_serializing_if = "Option::is_none")]
780    pub version: Option<String>,
781    #[serde(default, skip_serializing_if = "Option::is_none")]
782    pub transformation_id: Option<crate::refs::ObjectRef>,
783    pub intent: ModelMutationIntent,
784}
785
786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
787pub struct EntityInstanceAssetLinkListRequest {
788    #[serde(default, skip_serializing_if = "Option::is_none")]
789    pub session: Option<SessionSelector>,
790    #[serde(default, skip_serializing_if = "Option::is_none")]
791    pub instance_id: Option<crate::refs::ObjectRef>,
792    #[serde(default, skip_serializing_if = "Option::is_none")]
793    pub study: Option<SemanticInput<crate::study::StudySelector>>,
794    #[serde(default, skip_serializing_if = "Option::is_none")]
795    pub asset: Option<SemanticInput<crate::asset::AssetSelector>>,
796    #[serde(default, skip_serializing_if = "Option::is_none")]
797    pub version: Option<String>,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
801pub struct EntityInstanceDatasetsRequest {
802    #[serde(default, skip_serializing_if = "Option::is_none")]
803    pub session: Option<SessionSelector>,
804    pub instance_id: crate::refs::ObjectRef,
805}
806
807#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
808pub struct EntityInstanceDatasetLinkAddRequest {
809    #[serde(default, skip_serializing_if = "Option::is_none")]
810    pub session: Option<SessionSelector>,
811    pub study: SemanticInput<crate::study::StudySelector>,
812    pub instance_id: crate::refs::ObjectRef,
813    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
814    pub variable: SemanticInput<crate::dictionary::VariableSelector>,
815    #[serde(default, skip_serializing_if = "Option::is_none")]
816    pub version: Option<String>,
817    #[serde(default, skip_serializing_if = "Option::is_none")]
818    pub transformation_id: Option<crate::refs::ObjectRef>,
819    pub intent: ModelMutationIntent,
820}
821
822#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
823pub struct EntityInstanceDatasetLinkGetRequest {
824    #[serde(default, skip_serializing_if = "Option::is_none")]
825    pub session: Option<SessionSelector>,
826    pub study: SemanticInput<crate::study::StudySelector>,
827    pub instance_id: crate::refs::ObjectRef,
828    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
829    #[serde(default, skip_serializing_if = "Option::is_none")]
830    pub version: Option<String>,
831}
832
833#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
834pub struct EntityInstanceDatasetLinkListRequest {
835    #[serde(default, skip_serializing_if = "Option::is_none")]
836    pub session: Option<SessionSelector>,
837    pub study: SemanticInput<crate::study::StudySelector>,
838    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
839    #[serde(default, skip_serializing_if = "Option::is_none")]
840    pub version: Option<String>,
841}
842
843pub type ModelLinkResponse = SemanticMutationResponse;
844
845pub type EntityInstanceDatasetLinkSummary = SemanticLinkReadback;
846
847pub type EntityInstanceDatasetLinkReadbackResponse = SemanticLinkListResponse;
848
849#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
850pub struct ListRelationsRequest {
851    #[serde(default, skip_serializing_if = "Option::is_none")]
852    pub session: Option<SessionSelector>,
853    pub domain: DomainSelector,
854}
855
856#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
857pub struct GetRelationRequest {
858    #[serde(default, skip_serializing_if = "Option::is_none")]
859    pub session: Option<SessionSelector>,
860    pub relation: RelationSelector,
861}
862
863#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
864pub struct AddRelationRequest {
865    #[serde(default, skip_serializing_if = "Option::is_none")]
866    pub session: Option<SessionSelector>,
867    pub domain: DomainSelector,
868    pub name: PublicName,
869    pub subject: PublicName,
870    pub object: PublicName,
871    #[serde(default, skip_serializing_if = "Option::is_none")]
872    pub description: Option<String>,
873    #[serde(default, skip_serializing_if = "Option::is_none")]
874    pub agent_instructions: Option<String>,
875    #[serde(default, skip_serializing_if = "Option::is_none")]
876    pub ontology_namespace: Option<String>,
877    #[serde(default, skip_serializing_if = "Option::is_none")]
878    pub ontology_class: Option<String>,
879    pub intent: ModelMutationIntent,
880}
881
882#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
883pub struct RelationListResponse {
884    pub relations: Vec<RelationDefinitionSummary>,
885    #[serde(default, skip_serializing_if = "Option::is_none")]
886    pub next_cursor: Option<String>,
887    #[serde(default, skip_serializing_if = "Option::is_none")]
888    pub warnings: Option<ModelWarnings>,
889}
890
891#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
892pub struct RelationDetailResponse {
893    pub relation: RelationDefinitionDetail,
894    #[serde(default, skip_serializing_if = "Option::is_none")]
895    pub warnings: Option<ModelWarnings>,
896}
897
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
899pub struct RelationInstanceGetRequest {
900    #[serde(default, skip_serializing_if = "Option::is_none")]
901    pub session: Option<SessionSelector>,
902    pub relation_instance_id: crate::refs::ObjectRef,
903}
904
905#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
906pub struct RelationInstanceListRequest {
907    #[serde(default, skip_serializing_if = "Option::is_none")]
908    pub session: Option<SessionSelector>,
909    pub relation: RelationSelector,
910}
911
912#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
913pub struct RelationInstanceAddRequest {
914    #[serde(default, skip_serializing_if = "Option::is_none")]
915    pub session: Option<SessionSelector>,
916    pub relation: RelationSelector,
917    pub subject_instance_id: crate::refs::ObjectRef,
918    pub object_instance_id: crate::refs::ObjectRef,
919    #[serde(default, skip_serializing_if = "Option::is_none")]
920    pub transformation_id: Option<crate::refs::ObjectRef>,
921    #[serde(default, skip_serializing_if = "Option::is_none")]
922    pub valid_from: Option<String>,
923    #[serde(default, skip_serializing_if = "Option::is_none")]
924    pub valid_to: Option<String>,
925    #[serde(default, skip_serializing_if = "Option::is_none")]
926    pub note: Option<String>,
927    pub intent: ModelMutationIntent,
928}
929
930pub type RelationInstanceListResponse = SemanticInstanceListResponse;
931
932pub type RelationInstanceDetailResponse = SemanticInstanceDetailResponse;
933
934#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
935pub struct RelationInstanceMapAddRequest {
936    #[serde(default, skip_serializing_if = "Option::is_none")]
937    pub session: Option<SessionSelector>,
938    pub study: SemanticInput<crate::study::StudySelector>,
939    pub relation_instance_id: crate::refs::ObjectRef,
940    pub external_id: String,
941    #[serde(default, skip_serializing_if = "Option::is_none")]
942    pub transformation_id: Option<crate::refs::ObjectRef>,
943    pub intent: ModelMutationIntent,
944}
945
946#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
947pub struct RelationInstanceMapGetRequest {
948    #[serde(default, skip_serializing_if = "Option::is_none")]
949    pub session: Option<SessionSelector>,
950    pub study: SemanticInput<crate::study::StudySelector>,
951    pub relation: RelationSelector,
952    pub external_id: String,
953}
954
955#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
956pub struct RelationInstanceMapListRequest {
957    #[serde(default, skip_serializing_if = "Option::is_none")]
958    pub session: Option<SessionSelector>,
959    pub study: SemanticInput<crate::study::StudySelector>,
960    #[serde(default, skip_serializing_if = "Option::is_none")]
961    pub relation: Option<RelationSelector>,
962}
963
964pub type RelationInstanceMapResponse = SemanticMappingDetailResponse;
965
966pub type RelationInstanceMappingSummary = SemanticMappingReadback;
967
968pub type RelationInstanceMapListResponse = SemanticMappingListResponse;
969
970#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
971pub struct RelationInstanceAssetLinkAddRequest {
972    #[serde(default, skip_serializing_if = "Option::is_none")]
973    pub session: Option<SessionSelector>,
974    pub study: SemanticInput<crate::study::StudySelector>,
975    pub relation_instance_id: crate::refs::ObjectRef,
976    pub asset: SemanticInput<crate::asset::AssetSelector>,
977    #[serde(default, skip_serializing_if = "Option::is_none")]
978    pub version: Option<String>,
979    #[serde(default, skip_serializing_if = "Option::is_none")]
980    pub transformation_id: Option<crate::refs::ObjectRef>,
981    pub intent: ModelMutationIntent,
982}
983
984#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
985pub struct RelationInstanceAssetLinkListRequest {
986    #[serde(default, skip_serializing_if = "Option::is_none")]
987    pub session: Option<SessionSelector>,
988    #[serde(default, skip_serializing_if = "Option::is_none")]
989    pub relation_instance_id: Option<crate::refs::ObjectRef>,
990    #[serde(default, skip_serializing_if = "Option::is_none")]
991    pub study: Option<SemanticInput<crate::study::StudySelector>>,
992    #[serde(default, skip_serializing_if = "Option::is_none")]
993    pub asset: Option<SemanticInput<crate::asset::AssetSelector>>,
994    #[serde(default, skip_serializing_if = "Option::is_none")]
995    pub version: Option<String>,
996}
997
998pub type RelationInstanceAssetLinkSummary = SemanticLinkReadback;
999
1000pub type RelationInstanceAssetLinkListResponse = SemanticLinkListResponse;
1001
1002#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1003pub struct RelationInstanceDatasetLinkAddRequest {
1004    #[serde(default, skip_serializing_if = "Option::is_none")]
1005    pub session: Option<SessionSelector>,
1006    pub study: SemanticInput<crate::study::StudySelector>,
1007    pub relation_instance_id: crate::refs::ObjectRef,
1008    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
1009    pub subject_variable: SemanticInput<crate::dictionary::VariableSelector>,
1010    pub object_variable: SemanticInput<crate::dictionary::VariableSelector>,
1011    #[serde(default, skip_serializing_if = "Option::is_none")]
1012    pub relation_variable: Option<SemanticInput<crate::dictionary::VariableSelector>>,
1013    #[serde(default, skip_serializing_if = "Option::is_none")]
1014    pub version: Option<String>,
1015    #[serde(default, skip_serializing_if = "Option::is_none")]
1016    pub transformation_id: Option<crate::refs::ObjectRef>,
1017    pub intent: ModelMutationIntent,
1018}
1019
1020#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1021pub struct RelationInstanceDatasetLinkGetRequest {
1022    #[serde(default, skip_serializing_if = "Option::is_none")]
1023    pub session: Option<SessionSelector>,
1024    pub study: SemanticInput<crate::study::StudySelector>,
1025    pub relation_instance_id: crate::refs::ObjectRef,
1026    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
1027    #[serde(default, skip_serializing_if = "Option::is_none")]
1028    pub version: Option<String>,
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1032pub struct RelationInstanceDatasetLinkListRequest {
1033    #[serde(default, skip_serializing_if = "Option::is_none")]
1034    pub session: Option<SessionSelector>,
1035    pub study: SemanticInput<crate::study::StudySelector>,
1036    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
1037    #[serde(default, skip_serializing_if = "Option::is_none")]
1038    pub version: Option<String>,
1039}
1040
1041pub type RelationInstanceDatasetLinkSummary = SemanticLinkReadback;
1042
1043pub type RelationInstanceDatasetLinkReadbackResponse = SemanticLinkListResponse;
1044
1045/// Retains legacy names while accepting the canonical selectors used by the
1046/// catalogue. Resolution belongs to the retained application Session.
1047#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1048#[serde(untagged)]
1049pub enum SemanticInput<T> {
1050    Name(PublicName),
1051    Selector(T),
1052    Reference(crate::refs::ObjectRef),
1053}
1054impl<T> From<PublicName> for SemanticInput<T> {
1055    fn from(name: PublicName) -> Self {
1056        Self::Name(name)
1057    }
1058}
1059impl<T> SemanticInput<T> {
1060    pub fn into_legacy_name(self) -> Result<PublicName, &'static str> {
1061        match self {
1062            Self::Name(name) => Ok(name),
1063            _ => Err("Canonical semantic selectors require a Managed Session"),
1064        }
1065    }
1066}
1067
1068#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1069pub struct EntityInstanceEnsureFromDatasetRequest {
1070    #[serde(default, skip_serializing_if = "Option::is_none")]
1071    pub session: Option<SessionSelector>,
1072    pub study: SemanticInput<crate::study::StudySelector>,
1073    pub entity: EntitySelector,
1074    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
1075    #[serde(default, skip_serializing_if = "Option::is_none")]
1076    pub version: Option<String>,
1077    pub external_id_variable: SemanticInput<crate::dictionary::VariableSelector>,
1078    #[serde(default, skip_serializing_if = "Option::is_none")]
1079    pub label_template: Option<String>,
1080    pub intent: SemanticWorkflowIntent,
1081}
1082
1083#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1084pub struct RelationInstanceEnsureFromDatasetRequest {
1085    #[serde(default, skip_serializing_if = "Option::is_none")]
1086    pub session: Option<SessionSelector>,
1087    pub study: SemanticInput<crate::study::StudySelector>,
1088    pub relation: RelationSelector,
1089    pub dataset: SemanticInput<crate::dataset::DatasetSelector>,
1090    #[serde(default, skip_serializing_if = "Option::is_none")]
1091    pub version: Option<String>,
1092    pub relation_external_id_variable: SemanticInput<crate::dictionary::VariableSelector>,
1093    pub subject_external_id_variable: SemanticInput<crate::dictionary::VariableSelector>,
1094    pub object_external_id_variable: SemanticInput<crate::dictionary::VariableSelector>,
1095    #[serde(default, skip_serializing_if = "Option::is_none")]
1096    pub valid_from_variable: Option<SemanticInput<crate::dictionary::VariableSelector>>,
1097    #[serde(default, skip_serializing_if = "Option::is_none")]
1098    pub valid_to_variable: Option<SemanticInput<crate::dictionary::VariableSelector>>,
1099    pub intent: SemanticWorkflowIntent,
1100}
1101
1102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1103pub struct SemanticWorkflowIntent {
1104    #[serde(default)]
1105    pub dry_run: bool,
1106    #[serde(default)]
1107    pub create_transformation: bool,
1108}
1109
1110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1111pub struct EntityInstanceEnsureFromDatasetResponse {
1112    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1113    pub instances: Vec<SemanticInstanceReadback>,
1114    pub summary: EntityInstanceEnsureFromDatasetSummary,
1115    pub counts: EntityInstanceEnsureFromDatasetCounts,
1116    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1117    pub conflicts: Vec<SemanticWorkflowConflict>,
1118    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1119    pub rejected_rows: Vec<SemanticWorkflowRejectedRow>,
1120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1121    pub warnings: Vec<String>,
1122}
1123
1124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1125pub struct RelationInstanceEnsureFromDatasetResponse {
1126    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1127    pub instances: Vec<SemanticInstanceReadback>,
1128    pub summary: RelationInstanceEnsureFromDatasetSummary,
1129    pub counts: RelationInstanceEnsureFromDatasetCounts,
1130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1131    pub conflicts: Vec<SemanticWorkflowConflict>,
1132    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1133    pub missing_endpoints: Vec<RelationInstanceMissingEndpointSummary>,
1134    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1135    pub rejected_rows: Vec<SemanticWorkflowRejectedRow>,
1136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1137    pub warnings: Vec<String>,
1138}
1139
1140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1141pub struct EntityInstanceEnsureFromDatasetSummary {
1142    #[serde(default, skip_serializing_if = "Option::is_none")]
1143    pub references: Option<SemanticBatchReferences>,
1144    pub schema_version: String,
1145    pub operation: String,
1146    pub dry_run: bool,
1147    pub study: String,
1148    pub entity: EntitySelectorSummary,
1149    pub dataset: String,
1150    #[serde(default, skip_serializing_if = "Option::is_none")]
1151    pub requested_version: Option<String>,
1152    pub resolved_version: String,
1153    pub external_id_variable: String,
1154    #[serde(default, skip_serializing_if = "Option::is_none")]
1155    pub label_template: Option<String>,
1156    #[serde(default, skip_serializing_if = "Option::is_none")]
1157    pub transformation: Option<SemanticWorkflowTransformationSummary>,
1158}
1159
1160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1161pub struct RelationInstanceEnsureFromDatasetSummary {
1162    #[serde(default, skip_serializing_if = "Option::is_none")]
1163    pub references: Option<SemanticBatchReferences>,
1164    pub schema_version: String,
1165    pub operation: String,
1166    pub dry_run: bool,
1167    pub study: String,
1168    pub relation: RelationSelectorSummary,
1169    pub dataset: String,
1170    #[serde(default, skip_serializing_if = "Option::is_none")]
1171    pub requested_version: Option<String>,
1172    pub resolved_version: String,
1173    pub relation_external_id_variable: String,
1174    pub subject_external_id_variable: String,
1175    pub object_external_id_variable: String,
1176    #[serde(default, skip_serializing_if = "Option::is_none")]
1177    pub valid_from_variable: Option<String>,
1178    #[serde(default, skip_serializing_if = "Option::is_none")]
1179    pub valid_to_variable: Option<String>,
1180    #[serde(default, skip_serializing_if = "Option::is_none")]
1181    pub transformation: Option<SemanticWorkflowTransformationSummary>,
1182}
1183
1184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1185pub struct EntitySelectorSummary {
1186    pub domain: String,
1187    pub name: String,
1188}
1189
1190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1191pub struct RelationSelectorSummary {
1192    pub domain: String,
1193    pub name: String,
1194}
1195
1196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1197pub struct SemanticWorkflowTransformationSummary {
1198    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1199    pub inputs: Vec<crate::refs::VersionRef>,
1200    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1201    pub outputs: Vec<crate::refs::VersionRef>,
1202    pub transformation_id: i64,
1203    pub transformation: crate::refs::TransformationRef,
1204    pub transformation_type: String,
1205    #[serde(default, skip_serializing_if = "Option::is_none")]
1206    pub description: Option<String>,
1207    pub input_count: usize,
1208    pub output_count: usize,
1209}
1210
1211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1212pub struct EntityInstanceEnsureFromDatasetCounts {
1213    pub created_instances: usize,
1214    pub reused_instances: usize,
1215    pub created_study_mappings: usize,
1216    pub reused_study_mappings: usize,
1217    pub created_dataset_links: usize,
1218    pub reused_dataset_links: usize,
1219    pub rejected_rows: usize,
1220    pub conflicts: usize,
1221}
1222
1223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1224pub struct RelationInstanceEnsureFromDatasetCounts {
1225    pub created_instances: usize,
1226    pub reused_instances: usize,
1227    pub created_study_mappings: usize,
1228    pub reused_study_mappings: usize,
1229    pub created_dataset_links: usize,
1230    pub reused_dataset_links: usize,
1231    pub missing_endpoints: usize,
1232    pub rejected_rows: usize,
1233    pub conflicts: usize,
1234}
1235
1236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1237pub struct SemanticWorkflowConflict {
1238    pub external_id: String,
1239    pub message: String,
1240}
1241
1242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1243pub struct RelationInstanceMissingEndpointSummary {
1244    pub relation_external_id: String,
1245    pub endpoint_role: String,
1246    pub endpoint_external_id: String,
1247    pub entity: EntityRef,
1248}
1249
1250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1251pub struct SemanticWorkflowRejectedRow {
1252    pub row_number: usize,
1253    pub reason: String,
1254}
1255
1256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1257#[serde(tag = "kind", rename_all = "snake_case")]
1258pub enum EntityPredicate {
1259    Text(TextPredicate<EntityTextField>),
1260    Domain { domain: DomainSelector },
1261    Tag(TagPredicate),
1262}
1263
1264#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1265#[serde(tag = "kind", rename_all = "snake_case")]
1266pub enum RelationPredicate {
1267    Text(TextPredicate<RelationTextField>),
1268    Domain { domain: DomainSelector },
1269    SourceEntity { entity: EntitySelector },
1270    TargetEntity { entity: EntitySelector },
1271    Tag(TagPredicate),
1272}
1273
1274#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1275#[serde(rename_all = "snake_case")]
1276pub enum EntityTextField {
1277    Name,
1278    Description,
1279    OntologyClass,
1280}
1281
1282fn collect_entity_predicate_set_issues<P>(
1283    path: &str,
1284    predicates: &PredicateSet<P>,
1285    issues: &mut Vec<ValidationIssue>,
1286) {
1287    if let Err(message) = predicates.validate() {
1288        issues.push(ValidationIssue::new(
1289            path,
1290            ValidationIssueCode::InvalidValue,
1291            message,
1292        ));
1293    }
1294}
1295
1296fn collect_entity_predicate_issues(
1297    predicates: &mut PredicateSet<EntityPredicate>,
1298    issues: &mut Vec<ValidationIssue>,
1299) {
1300    for (index, predicate) in predicates.predicates_mut().iter_mut().enumerate() {
1301        if let EntityPredicate::Text(text) = predicate
1302            && let Err(message) = text.validate()
1303        {
1304            issues.push(ValidationIssue::new(
1305                format!("body.predicates.predicates[{index}].value"),
1306                ValidationIssueCode::InvalidValue,
1307                message,
1308            ));
1309        }
1310    }
1311}
1312
1313fn collect_entity_cursor_issues(
1314    request: &SearchEntitiesRequest,
1315    protocol_version: &ProtocolVersion,
1316    issues: &mut Vec<ValidationIssue>,
1317) {
1318    if request.page.cursor.is_some()
1319        && let Err(error) = request.decoded_cursor(protocol_version)
1320    {
1321        issues.extend(error.issues.iter().cloned());
1322    }
1323}
1324
1325fn collect_relation_predicate_issues(
1326    predicates: &mut PredicateSet<RelationPredicate>,
1327    issues: &mut Vec<ValidationIssue>,
1328) {
1329    for (index, predicate) in predicates.predicates_mut().iter_mut().enumerate() {
1330        if let RelationPredicate::Text(text) = predicate
1331            && let Err(message) = text.validate()
1332        {
1333            issues.push(ValidationIssue::new(
1334                format!("body.predicates.predicates[{index}].value"),
1335                ValidationIssueCode::InvalidValue,
1336                message,
1337            ));
1338        }
1339    }
1340}
1341
1342fn collect_relation_cursor_issues(
1343    request: &SearchRelationsRequest,
1344    protocol_version: &ProtocolVersion,
1345    issues: &mut Vec<ValidationIssue>,
1346) {
1347    if request.page.cursor.is_some()
1348        && let Err(error) = request.decoded_cursor(protocol_version)
1349    {
1350        issues.extend(error.issues.iter().cloned());
1351    }
1352}
1353
1354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1355#[serde(rename_all = "snake_case")]
1356pub enum RelationTextField {
1357    Name,
1358    Description,
1359    SourceEntityName,
1360    TargetEntityName,
1361    OntologyClass,
1362}
1363
1364/// Canonical scope retained by an admitted batch; names remain display values.
1365#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1366pub struct SemanticBatchReferences {
1367    pub study: crate::refs::StudyRef,
1368    pub definition: crate::refs::ObjectRef,
1369    pub dataset: crate::refs::DatasetRef,
1370    pub version: crate::refs::VersionRef,
1371    pub variables: Vec<crate::refs::VariableRef>,
1372}
1373/// Content-bearing record projection, delivered only by a Session capability.
1374#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1375pub struct SemanticInstanceReadback {
1376    pub instance: crate::refs::ObjectRef,
1377    pub definition: crate::refs::ObjectRef,
1378    pub transformation: crate::refs::TransformationRef,
1379    #[serde(default, skip_serializing_if = "Option::is_none")]
1380    pub label: Option<String>,
1381    #[serde(default, skip_serializing_if = "Option::is_none")]
1382    pub note: Option<String>,
1383    #[serde(default, skip_serializing_if = "Option::is_none")]
1384    pub subject: Option<crate::refs::EntityInstanceRef>,
1385    #[serde(default, skip_serializing_if = "Option::is_none")]
1386    pub object: Option<crate::refs::EntityInstanceRef>,
1387    #[serde(default, skip_serializing_if = "Option::is_none")]
1388    pub valid_from: Option<chrono::NaiveDate>,
1389    #[serde(default, skip_serializing_if = "Option::is_none")]
1390    pub valid_to: Option<chrono::NaiveDate>,
1391}
1392
1393/// Admitted instance content. Every public endpoint retains its datastore origin.
1394#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1395pub struct SemanticInstanceDetailResponse {
1396    pub instance: SemanticInstanceReadback,
1397}
1398#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1399pub struct SemanticInstanceListResponse {
1400    pub instances: Vec<SemanticInstanceReadback>,
1401}
1402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1403pub struct SemanticMappingReadback {
1404    pub study: crate::refs::StudyRef,
1405    pub definition: crate::refs::ObjectRef,
1406    pub instance: SemanticInstanceReadback,
1407    pub external_id: String,
1408    pub transformation: crate::refs::TransformationRef,
1409}
1410#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1411pub struct SemanticMappingDetailResponse {
1412    /// Missing mappings are admitted content outcomes, represented explicitly.
1413    pub instance: Option<SemanticInstanceReadback>,
1414    pub mapping: Option<SemanticMappingReadback>,
1415}
1416#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1417pub struct SemanticMappingListResponse {
1418    pub study: crate::refs::StudyRef,
1419    pub mappings: Vec<SemanticMappingReadback>,
1420}
1421#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1422pub struct SemanticLinkReadback {
1423    pub version: crate::refs::VersionRef,
1424    pub asset: crate::refs::AssetRef,
1425    pub study: crate::refs::StudyRef,
1426    pub instance: SemanticInstanceReadback,
1427    pub transformation: crate::refs::TransformationRef,
1428    #[serde(default, skip_serializing_if = "Option::is_none")]
1429    pub mapping: Option<SemanticMappingReadback>,
1430    #[serde(default, skip_serializing_if = "Option::is_none")]
1431    pub variable: Option<crate::refs::VariableRef>,
1432    #[serde(default, skip_serializing_if = "Option::is_none")]
1433    pub subject_variable: Option<crate::refs::VariableRef>,
1434    #[serde(default, skip_serializing_if = "Option::is_none")]
1435    pub object_variable: Option<crate::refs::VariableRef>,
1436    #[serde(default, skip_serializing_if = "Option::is_none")]
1437    pub relation_variable: Option<crate::refs::VariableRef>,
1438}
1439#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1440pub struct SemanticLinkListResponse {
1441    pub links: Vec<SemanticLinkReadback>,
1442}
1443/// Identity-only acknowledgement of a manual write. Content remains subject to
1444/// admission; supplied labels or external IDs are never echoed by this receipt.
1445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1446pub struct SemanticMutationResponse {
1447    #[serde(flatten)]
1448    pub target: SemanticMutationTarget,
1449    pub content_withheld: bool,
1450    pub transformation: SemanticMutationAttribution,
1451}
1452#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1453pub struct SemanticMutationAttribution {
1454    pub transformation: crate::refs::TransformationRef,
1455    pub inputs: Vec<crate::refs::VersionRef>,
1456    pub outputs: Vec<crate::refs::VersionRef>,
1457}
1458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1459#[serde(untagged)]
1460pub enum SemanticMutationTarget {
1461    Instance { instance: SemanticInstanceIdentity },
1462    Mapping { mapping: SemanticMappingIdentity },
1463    Link { link: SemanticLinkIdentity },
1464}
1465#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1466pub struct SemanticInstanceIdentity {
1467    pub instance: crate::refs::ObjectRef,
1468    pub definition: crate::refs::ObjectRef,
1469}
1470#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1471pub struct SemanticMappingIdentity {
1472    pub study: crate::refs::StudyRef,
1473    pub instance: crate::refs::ObjectRef,
1474    pub transformation: crate::refs::TransformationRef,
1475}
1476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1477pub struct SemanticLinkIdentity {
1478    pub version: crate::refs::VersionRef,
1479    pub instance: crate::refs::ObjectRef,
1480    pub transformation: crate::refs::TransformationRef,
1481    #[serde(default, skip_serializing_if = "Option::is_none")]
1482    pub variable: Option<crate::refs::VariableRef>,
1483    #[serde(default, skip_serializing_if = "Option::is_none")]
1484    pub subject_variable: Option<crate::refs::VariableRef>,
1485    #[serde(default, skip_serializing_if = "Option::is_none")]
1486    pub object_variable: Option<crate::refs::VariableRef>,
1487    #[serde(default, skip_serializing_if = "Option::is_none")]
1488    pub relation_variable: Option<crate::refs::VariableRef>,
1489}