Skip to main content

dpm_model/
ownership.rs

1//! Who held a task, and when: the append-only records independent review is judged against.
2
3use crate::{ActorId, WorkItem};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Authorized transfer of claimed or started work between owners.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct Handoff {
12    /// Owner the work was taken from.
13    pub from: ActorId,
14    /// Owner the work was given to.
15    pub to: ActorId,
16    /// Human or service that authorized the transfer.
17    pub actor: ActorId,
18    /// Caller-supplied UTC operation time.
19    pub at: DateTime<Utc>,
20    /// Why the executor changed.
21    pub reason: String,
22}
23
24/// An owner giving back an unstarted claim.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct ClaimRelease {
28    /// Owner that gave the claim back.
29    pub actor: ActorId,
30    /// Caller-supplied UTC operation time.
31    pub at: DateTime<Utc>,
32    /// Why the claim was given back.
33    pub reason: String,
34}
35
36/// The record through which an actor held a task at or before some time.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Holding {
39    /// Owned it then, and owns it now or until no later handoff.
40    Owner,
41    /// Owned it then and handed it on at this later time.
42    OwnerUntil(DateTime<Utc>),
43    /// Received it by the handoff at this time.
44    HandedTo(DateTime<Utc>),
45    /// Gave it away by the handoff at this time.
46    HandedFrom(DateTime<Utc>),
47    /// Gave back a claim on it at this time.
48    Released(DateTime<Utc>),
49}
50
51impl fmt::Display for Holding {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            Self::Owner => write!(f, "owned the task at that time"),
55            Self::OwnerUntil(at) => write!(f, "owned the task then, until the handoff at {at}"),
56            Self::HandedTo(at) => write!(f, "received the task by the handoff at {at}"),
57            Self::HandedFrom(at) => write!(f, "held the task until the handoff at {at}"),
58            Self::Released(at) => write!(f, "released its claim on the task at {at}"),
59        }
60    }
61}
62
63impl WorkItem {
64    /// Whether the actor owns this work now or held it before a handoff or release.
65    ///
66    /// Independent review reads this rather than the current owner alone, so handing work away
67    /// or releasing a claim never turns an actor that held it into its reviewer.
68    #[must_use]
69    pub fn held_by(&self, actor: &ActorId) -> bool {
70        self.execution.owner.as_ref() == Some(actor)
71            || self.execution.handoffs.iter().any(|h| h.from == *actor)
72            || self.execution.releases.iter().any(|r| r.actor == *actor)
73    }
74
75    /// How the actor held this work at or before `at`, if it did.
76    ///
77    /// A recorded review stays independent after its reviewer later takes the work over, so the
78    /// records are judged at their own time. Only started work is reviewed and a release only
79    /// ends an unstarted claim, so the current owner with no later handoff has held the work
80    /// since before any review. A handoff at the review instant counts as preceding it, because
81    /// equal times cannot be ordered and independence must not be assumed.
82    #[must_use]
83    pub fn holding_at(&self, actor: &ActorId, at: DateTime<Utc>) -> Option<Holding> {
84        let earlier = self.execution.handoffs.iter().filter(|h| h.at <= at);
85        if let Some(h) = earlier.clone().find(|h| h.to == *actor) {
86            return Some(Holding::HandedTo(h.at));
87        }
88        if let Some(h) = earlier.clone().find(|h| h.from == *actor) {
89            return Some(Holding::HandedFrom(h.at));
90        }
91        if let Some(r) = self
92            .execution
93            .releases
94            .iter()
95            .find(|r| r.at <= at && r.actor == *actor)
96        {
97            return Some(Holding::Released(r.at));
98        }
99        match self.execution.handoffs.iter().find(|h| h.at > at) {
100            Some(later) if later.from == *actor => Some(Holding::OwnerUntil(later.at)),
101            None if self.execution.owner.as_ref() == Some(actor) => Some(Holding::Owner),
102            _ => None,
103        }
104    }
105}