use super::OptionValueExt;
use meerkat_machine_dsl::machine;
#[derive(
Debug,
Clone,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Serialize,
serde::Deserialize,
)]
pub struct WorkAttentionBindingKey(pub String);
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Serialize,
serde::Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum WorkAttentionMode {
#[default]
Pursue,
Coordinate,
Review,
Falsify,
Judge,
Observe,
}
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Serialize,
serde::Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum AttentionDelegatedAuthority {
#[default]
AddEvidence,
CloseOwnReviewItem,
RequestClosure,
CloseIfPolicyAllows,
}
machine! {
machine WorkAttentionLifecycleMachine {
version: 1,
rust: "self" / "catalog::dsl::work_attention_lifecycle",
state {
lifecycle_phase: WorkAttentionLifecycleState,
revision: u64,
paused_until_utc_ms: Option<u64>,
superseded_by_binding_key: Option<WorkAttentionBindingKey>,
terminal_at_utc_ms: Option<u64>,
}
init(Active) {
revision = 1,
paused_until_utc_ms = None,
superseded_by_binding_key = None,
terminal_at_utc_ms = None,
}
terminal [Superseded, Stopped]
phase WorkAttentionLifecycleState {
Active,
Paused,
Superseded,
Stopped,
}
input WorkAttentionLifecycleInput {
Pause { expected_revision: u64, until_utc_ms: Option<u64> },
Resume { expected_revision: u64 },
Supersede { expected_revision: u64, superseded_by_binding_key: WorkAttentionBindingKey, at_utc_ms: u64 },
Stop { expected_revision: u64, at_utc_ms: u64 },
ClassifyAttentionEligibility { now_utc_ms: u64 },
ClassifyAttentionAuthority {
mode: Enum<WorkAttentionMode>,
delegated_authority: Enum<AttentionDelegatedAuthority>,
},
}
effect WorkAttentionLifecycleEffect {
AttentionPaused { revision: u64 },
AttentionResumed { revision: u64 },
AttentionSuperseded { revision: u64 },
AttentionStopped { revision: u64 },
AttentionEligibilityClassified { eligible: bool },
AttentionAuthorityClassified {
can_get: bool,
can_add_evidence: bool,
can_release: bool,
can_update: bool,
can_block: bool,
can_create: bool,
can_link: bool,
can_link_parent: bool,
can_link_related: bool,
can_link_derived_from: bool,
can_close_own_review_item: bool,
can_close_if_policy_allows: bool,
},
}
helper attention_is_adversarial(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Review
|| mode == WorkAttentionMode::Falsify
|| mode == WorkAttentionMode::Observe
}
helper attention_can_get(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Pursue
|| mode == WorkAttentionMode::Coordinate
|| mode == WorkAttentionMode::Review
|| mode == WorkAttentionMode::Falsify
|| mode == WorkAttentionMode::Judge
|| mode == WorkAttentionMode::Observe
}
helper attention_can_add_evidence(mode: WorkAttentionMode) -> bool {
mode != WorkAttentionMode::Observe
}
helper attention_can_release(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Pursue
}
helper attention_can_update(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Pursue || mode == WorkAttentionMode::Coordinate
}
helper attention_can_block(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Pursue
}
helper attention_can_create(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Coordinate
}
helper attention_can_link(mode: WorkAttentionMode) -> bool {
mode == WorkAttentionMode::Coordinate
}
helper attention_can_link_parent(mode: WorkAttentionMode) -> bool {
attention_can_link(mode)
}
helper attention_can_link_related(mode: WorkAttentionMode) -> bool {
attention_can_link(mode)
}
helper attention_can_link_derived_from(mode: WorkAttentionMode) -> bool {
attention_can_link(mode)
}
helper attention_can_close_own_review_item(
mode: WorkAttentionMode,
delegated_authority: AttentionDelegatedAuthority
) -> bool {
(mode == WorkAttentionMode::Review || mode == WorkAttentionMode::Falsify)
&& delegated_authority == AttentionDelegatedAuthority::CloseOwnReviewItem
}
helper attention_can_close_if_policy_allows(
mode: WorkAttentionMode,
delegated_authority: AttentionDelegatedAuthority
) -> bool {
delegated_authority == AttentionDelegatedAuthority::CloseIfPolicyAllows
&& attention_is_adversarial(mode) == false
}
invariant live_has_no_terminal_time {
(self.lifecycle_phase != Phase::Active && self.lifecycle_phase != Phase::Paused)
|| self.terminal_at_utc_ms == None
}
invariant paused_has_pause_state {
self.lifecycle_phase == Phase::Paused || self.paused_until_utc_ms == None
}
invariant superseded_records_successor {
self.lifecycle_phase != Phase::Superseded || self.superseded_by_binding_key != None
}
disposition AttentionPaused => local seam NoOwnerRealization,
disposition AttentionResumed => local seam NoOwnerRealization,
disposition AttentionSuperseded => local seam NoOwnerRealization,
disposition AttentionStopped => local seam NoOwnerRealization,
disposition AttentionEligibilityClassified => local seam SurfaceResultAlignment,
disposition AttentionAuthorityClassified => local seam SurfaceResultAlignment,
transition PauseActive {
on input Pause { expected_revision, until_utc_ms }
guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
update {
self.paused_until_utc_ms = until_utc_ms;
self.revision += 1;
}
to Paused
emit AttentionPaused { revision: self.revision }
}
transition PausePaused {
on input Pause { expected_revision, until_utc_ms }
guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
update {
self.paused_until_utc_ms = until_utc_ms;
self.revision += 1;
}
to Paused
emit AttentionPaused { revision: self.revision }
}
transition ResumePaused {
on input Resume { expected_revision }
guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
update {
self.paused_until_utc_ms = None;
self.revision += 1;
}
to Active
emit AttentionResumed { revision: self.revision }
}
transition SupersedeActive {
on input Supersede { expected_revision, superseded_by_binding_key, at_utc_ms }
guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
update {
self.paused_until_utc_ms = None;
self.superseded_by_binding_key = Some(superseded_by_binding_key);
self.terminal_at_utc_ms = Some(at_utc_ms);
self.revision += 1;
}
to Superseded
emit AttentionSuperseded { revision: self.revision }
}
transition SupersedePaused {
on input Supersede { expected_revision, superseded_by_binding_key, at_utc_ms }
guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
update {
self.paused_until_utc_ms = None;
self.superseded_by_binding_key = Some(superseded_by_binding_key);
self.terminal_at_utc_ms = Some(at_utc_ms);
self.revision += 1;
}
to Superseded
emit AttentionSuperseded { revision: self.revision }
}
transition StopActive {
on input Stop { expected_revision, at_utc_ms }
guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
update {
self.paused_until_utc_ms = None;
self.terminal_at_utc_ms = Some(at_utc_ms);
self.revision += 1;
}
to Stopped
emit AttentionStopped { revision: self.revision }
}
transition StopPaused {
on input Stop { expected_revision, at_utc_ms }
guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
update {
self.paused_until_utc_ms = None;
self.terminal_at_utc_ms = Some(at_utc_ms);
self.revision += 1;
}
to Stopped
emit AttentionStopped { revision: self.revision }
}
transition ClassifyEligibilityActive {
on input ClassifyAttentionEligibility { now_utc_ms }
guard "active_is_eligible" { self.lifecycle_phase == Phase::Active }
update {}
to Active
emit AttentionEligibilityClassified { eligible: true }
}
transition ClassifyEligibilityPausedElapsed {
on input ClassifyAttentionEligibility { now_utc_ms }
guard "paused" { self.lifecycle_phase == Phase::Paused }
guard "deadline_elapsed" {
self.paused_until_utc_ms != None
&& self.paused_until_utc_ms.get("value") <= now_utc_ms
}
update {}
to Paused
emit AttentionEligibilityClassified { eligible: true }
}
transition ClassifyEligibilityPausedPending {
on input ClassifyAttentionEligibility { now_utc_ms }
guard "paused" { self.lifecycle_phase == Phase::Paused }
guard "deadline_not_elapsed" {
self.paused_until_utc_ms == None
|| self.paused_until_utc_ms.get("value") > now_utc_ms
}
update {}
to Paused
emit AttentionEligibilityClassified { eligible: false }
}
transition ClassifyEligibilitySuperseded {
on input ClassifyAttentionEligibility { now_utc_ms }
guard "superseded_is_ineligible" { self.lifecycle_phase == Phase::Superseded }
update {}
to Superseded
emit AttentionEligibilityClassified { eligible: false }
}
transition ClassifyEligibilityStopped {
on input ClassifyAttentionEligibility { now_utc_ms }
guard "stopped_is_ineligible" { self.lifecycle_phase == Phase::Stopped }
update {}
to Stopped
emit AttentionEligibilityClassified { eligible: false }
}
transition ClassifyAuthority {
per_phase [Active, Paused, Superseded, Stopped]
on input ClassifyAttentionAuthority { mode, delegated_authority }
update {}
to Active
emit AttentionAuthorityClassified {
can_get: attention_can_get(mode),
can_add_evidence: attention_can_add_evidence(mode),
can_release: attention_can_release(mode),
can_update: attention_can_update(mode),
can_block: attention_can_block(mode),
can_create: attention_can_create(mode),
can_link: attention_can_link(mode),
can_link_parent: attention_can_link_parent(mode),
can_link_related: attention_can_link_related(mode),
can_link_derived_from: attention_can_link_derived_from(mode),
can_close_own_review_item: attention_can_close_own_review_item(mode, delegated_authority),
can_close_if_policy_allows: attention_can_close_if_policy_allows(mode, delegated_authority)
}
}
}
}
impl serde::Serialize for WorkAttentionLifecycleState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(match self {
Self::Active => "active",
Self::Paused => "paused",
Self::Superseded => "superseded",
Self::Stopped => "stopped",
})
}
}
impl<'de> serde::Deserialize<'de> for WorkAttentionLifecycleState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <String as serde::Deserialize>::deserialize(deserializer)?;
match value.as_str() {
"active" | "Active" => Ok(Self::Active),
"paused" | "Paused" => Ok(Self::Paused),
"superseded" | "Superseded" => Ok(Self::Superseded),
"stopped" | "Stopped" => Ok(Self::Stopped),
other => Err(serde::de::Error::custom(format!(
"invalid WorkAttentionLifecycleState `{other}`"
))),
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct WorkAttentionLifecycleMachineStateWire {
lifecycle_phase: WorkAttentionLifecycleState,
revision: u64,
#[serde(default)]
paused_until_utc_ms: Option<u64>,
#[serde(default)]
superseded_by_binding_key: Option<WorkAttentionBindingKey>,
#[serde(default)]
terminal_at_utc_ms: Option<u64>,
}
impl From<&WorkAttentionLifecycleMachineState> for WorkAttentionLifecycleMachineStateWire {
fn from(state: &WorkAttentionLifecycleMachineState) -> Self {
Self {
lifecycle_phase: state.lifecycle_phase,
revision: state.revision,
paused_until_utc_ms: state.paused_until_utc_ms,
superseded_by_binding_key: state.superseded_by_binding_key.clone(),
terminal_at_utc_ms: state.terminal_at_utc_ms,
}
}
}
impl From<WorkAttentionLifecycleMachineStateWire> for WorkAttentionLifecycleMachineState {
fn from(wire: WorkAttentionLifecycleMachineStateWire) -> Self {
Self {
lifecycle_phase: wire.lifecycle_phase,
revision: wire.revision,
paused_until_utc_ms: wire.paused_until_utc_ms,
superseded_by_binding_key: wire.superseded_by_binding_key,
terminal_at_utc_ms: wire.terminal_at_utc_ms,
}
}
}
impl serde::Serialize for WorkAttentionLifecycleMachineState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
WorkAttentionLifecycleMachineStateWire::from(self).serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for WorkAttentionLifecycleMachineState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
WorkAttentionLifecycleMachineStateWire::deserialize(deserializer).map(Self::from)
}
}