Skip to main content

made_core/entities/
task.rs

1//! [`Task`] entity — a unit of work submitted for deliberation.
2
3use serde::{Deserialize, Serialize};
4
5use crate::entities::{ExternalContextBundle, TaskConstraints, TaskMetadata};
6use crate::value_objects::{Attributes, Specialty, TaskDescription, TaskId};
7
8/// A task submitted to MADE.
9///
10/// `description` is the free-form prompt that agents consume;
11/// `attributes` carries arbitrary, opaque domain data that
12/// MADE does not interpret. The `specialty` selects which
13/// council deliberates.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Task {
16    id: TaskId,
17    specialty: Specialty,
18    description: TaskDescription,
19    constraints: TaskConstraints,
20    attributes: Attributes,
21    external_context: Option<ExternalContextBundle>,
22    #[serde(default)]
23    metadata: TaskMetadata,
24}
25
26impl Task {
27    #[must_use]
28    pub fn new(
29        id: TaskId,
30        specialty: Specialty,
31        description: TaskDescription,
32        constraints: TaskConstraints,
33        attributes: Attributes,
34    ) -> Self {
35        Self::new_with_context(id, specialty, description, constraints, attributes, None)
36    }
37
38    #[must_use]
39    pub fn new_with_context(
40        id: TaskId,
41        specialty: Specialty,
42        description: TaskDescription,
43        constraints: TaskConstraints,
44        attributes: Attributes,
45        external_context: Option<ExternalContextBundle>,
46    ) -> Self {
47        Self::new_with_metadata(
48            id,
49            specialty,
50            description,
51            constraints,
52            attributes,
53            external_context,
54            TaskMetadata::default(),
55        )
56    }
57
58    #[must_use]
59    pub fn new_with_metadata(
60        id: TaskId,
61        specialty: Specialty,
62        description: TaskDescription,
63        constraints: TaskConstraints,
64        attributes: Attributes,
65        external_context: Option<ExternalContextBundle>,
66        metadata: TaskMetadata,
67    ) -> Self {
68        Self {
69            id,
70            specialty,
71            description,
72            constraints,
73            attributes,
74            external_context,
75            metadata,
76        }
77    }
78
79    #[must_use]
80    pub fn id(&self) -> &TaskId {
81        &self.id
82    }
83    #[must_use]
84    pub fn specialty(&self) -> &Specialty {
85        &self.specialty
86    }
87    #[must_use]
88    pub fn description(&self) -> &TaskDescription {
89        &self.description
90    }
91    #[must_use]
92    pub fn constraints(&self) -> &TaskConstraints {
93        &self.constraints
94    }
95    #[must_use]
96    pub fn attributes(&self) -> &Attributes {
97        &self.attributes
98    }
99
100    #[must_use]
101    pub fn external_context(&self) -> Option<&ExternalContextBundle> {
102        self.external_context.as_ref()
103    }
104
105    #[must_use]
106    pub fn metadata(&self) -> &TaskMetadata {
107        &self.metadata
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::value_objects::{Attributes, Specialty, TaskDescription, TaskId};
115
116    fn make() -> Task {
117        Task::new(
118            TaskId::new("t1").unwrap(),
119            Specialty::new("triage").unwrap(),
120            TaskDescription::new("investigate alert").unwrap(),
121            TaskConstraints::default(),
122            Attributes::empty(),
123        )
124    }
125
126    #[test]
127    fn task_accessors_return_fields() {
128        let t = make();
129        assert_eq!(t.id().as_str(), "t1");
130        assert_eq!(t.specialty().as_str(), "triage");
131        assert_eq!(t.description().as_str(), "investigate alert");
132        assert!(t.attributes().is_empty());
133        assert!(t.external_context().is_none());
134        assert_eq!(t.metadata(), &TaskMetadata::default());
135    }
136
137    #[test]
138    fn task_has_no_hardcoded_domain_vocabulary() {
139        // Regression: neutral specialty + neutral description must
140        // form a valid Task.
141        let _ = Task::new(
142            TaskId::new("t-clinical-01").unwrap(),
143            Specialty::new("clinical-intake").unwrap(),
144            TaskDescription::new("classify protocol deviation").unwrap(),
145            TaskConstraints::default(),
146            Attributes::empty(),
147        );
148    }
149}