1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Object-centric conformance claims — [`crate::interop::ConformanceTriple`]
//! *scoped per object type*, never smuggled through a single flat claim.
//!
//! [`crate::interop::InteropRefusal::FlatClaimOverObjectCentric`] already
//! refuses an [`crate::interop::ArtifactGrounding`] that claims a flat
//! (XES-style) shape over object-centric (OCEL-style) data. This module gives
//! the *positive* counterpart: the shape a legitimate object-centric
//! conformance claim takes — one [`crate::interop::ConformanceTriple`] per
//! object type, not one triple for the whole object-centric log.
//!
//! ## What this module **IS**
//!
//! - [`ObjectTypeConformance`]: one conformance-dimension claim scoped to one
//! object type, reusing [`crate::interop::ConformanceTriple`] rather than
//! duplicating its fields.
//! - [`ObjectCentricConformanceClaim`]: a set of per-object-type claims,
//! grounded to a log reference.
//! - [`ObjectCentricConformanceRefusal`]: first-class, specifically named
//! refusals.
//!
//! ## What this module is **NOT**
//!
//! - **Not** a conformance checker. It never measures fitness or precision
//! for any object type; it only enforces that a claim is grounded and
//! properly scoped per type.
//!
//! ## Graduation
//!
//! Actually computing per-object-type fitness/precision over an object-centric
//! log is a `wasm4pm` job. This module only states and refuses the
//! object-centric conformance-claim *shape*.
use crate::interop::ConformanceTriple;
/// One conformance-dimension claim scoped to one object type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectTypeConformance {
/// The object type this claim is scoped to (e.g. `"order"`, `"item"`).
pub object_type: String,
/// The conformance dimensions claimed for this object type. No values
/// measured — see [`crate::interop::ConformanceTriple`].
pub triple: ConformanceTriple,
}
/// A set of per-object-type conformance claims, grounded to a log reference.
///
/// A single flat [`crate::interop::ConformanceTriple`] cannot represent this —
/// that is exactly what
/// [`crate::interop::InteropRefusal::FlatClaimOverObjectCentric`] refuses.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectCentricConformanceClaim {
/// One entry per object type this claim covers.
pub per_type: Vec<ObjectTypeConformance>,
/// An opaque reference naming the object-centric log this claim is made
/// against (structure-only: never dereferenced here).
pub log_ref: String,
}
impl ObjectCentricConformanceClaim {
/// Claims `per_type` against `log_ref`.
#[must_use]
pub fn new(per_type: Vec<ObjectTypeConformance>, log_ref: impl Into<String>) -> Self {
Self {
per_type,
log_ref: log_ref.into(),
}
}
/// Whether the claim is grounded: a non-empty `log_ref`, at least one
/// scoped object type, every entry names a non-empty `object_type`, and
/// every entry's `triple` is itself grounded
/// ([`crate::interop::ConformanceTriple::is_grounded`]).
///
/// ```
/// use wasm4pm_compat::object_centric_conformance::ObjectCentricConformanceClaim;
/// assert!(!ObjectCentricConformanceClaim::new(vec![], "log:1").is_grounded());
/// ```
#[must_use]
pub fn is_grounded(&self) -> bool {
!self.log_ref.trim().is_empty()
&& !self.per_type.is_empty()
&& self
.per_type
.iter()
.all(|e| !e.object_type.trim().is_empty() && e.triple.is_grounded())
}
/// Admits this claim, or refuses with a specific named law.
///
/// ```
/// use wasm4pm_compat::interop::ConformanceTriple;
/// use wasm4pm_compat::object_centric_conformance::{
/// ObjectCentricConformanceClaim, ObjectCentricConformanceRefusal, ObjectTypeConformance,
/// };
/// let claim = ObjectCentricConformanceClaim::new(vec![], "log:1");
/// assert_eq!(
/// claim.admit_flat(),
/// Err(ObjectCentricConformanceRefusal::NoObjectTypesScoped)
/// );
/// ```
#[must_use = "check the admission result"]
pub fn admit_flat(&self) -> Result<(), ObjectCentricConformanceRefusal> {
if self.log_ref.trim().is_empty() {
return Err(ObjectCentricConformanceRefusal::UngroundedClaim);
}
if self.per_type.is_empty() {
return Err(ObjectCentricConformanceRefusal::NoObjectTypesScoped);
}
for entry in &self.per_type {
if entry.object_type.trim().is_empty() || !entry.triple.is_grounded() {
return Err(ObjectCentricConformanceRefusal::UnscopedObjectType);
}
}
Ok(())
}
}
/// First-class, specifically named refusals for the object-centric
/// conformance-claim grammar.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ObjectCentricConformanceRefusal {
/// An [`ObjectCentricConformanceClaim`] carries an empty `log_ref`.
UngroundedClaim,
/// An [`ObjectCentricConformanceClaim`] carries no object-type entries at
/// all — a flat, unscoped claim over object-centric data.
NoObjectTypesScoped,
/// An [`ObjectTypeConformance`] entry carries an empty `object_type` or an
/// ungrounded `triple`.
UnscopedObjectType,
}
impl core::fmt::Display for ObjectCentricConformanceRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
ObjectCentricConformanceRefusal::UngroundedClaim => "UngroundedClaim",
ObjectCentricConformanceRefusal::NoObjectTypesScoped => "NoObjectTypesScoped",
ObjectCentricConformanceRefusal::UnscopedObjectType => "UnscopedObjectType",
};
write!(f, "object-centric conformance refusal: {law}")
}
}