Skip to main content

imp_core/
contracts.rs

1//! imp-owned worker and evidence contract types.
2//!
3//! These DTOs define the boundary between imp's mana worker runtime,
4//! and future runner surfaces. They used to live in the experimental
5//! the earlier experimental contracts crate, but currently only imp consumes
6//! them, so they stay
7//! local until a real cross-repo/versioned protocol boundary is needed.
8
9use std::path::PathBuf;
10
11use serde::{Deserialize, Serialize};
12
13/// A previous attempt on a worker-assigned unit.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct WorkerAttempt {
16    pub number: u32,
17    pub outcome: String,
18    pub summary: String,
19}
20
21/// Everything needed to execute a unit as a worker.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct WorkerAssignment {
24    /// The unit ID (e.g. "28.1.2").
25    pub id: String,
26    /// Unit title.
27    pub title: String,
28    /// Combined description (frontmatter + body).
29    pub description: String,
30    /// Supplemental design guidance, if any.
31    pub design: Option<String>,
32    /// Acceptance criteria, if any.
33    pub acceptance: Option<String>,
34    /// Verify command, if any.
35    pub verify: Option<String>,
36    /// Effective verify timeout in seconds, if any.
37    pub verify_timeout_secs: Option<u64>,
38    /// Whether the verify gate was proven failing before work started.
39    pub fail_first: bool,
40    /// Unit notes (progress, diagnosis, etc.).
41    pub notes: Option<String>,
42    /// Unresolved decisions.
43    pub decisions: Vec<String>,
44    /// Dependency IDs.
45    pub dependencies: Vec<String>,
46    /// File paths declared on the unit.
47    pub paths: Vec<String>,
48    /// Explicit file references for context prefill.
49    pub files: Vec<String>,
50    /// Structured attempt history.
51    pub attempts: Vec<WorkerAttempt>,
52    /// Workspace root (parent of .mana/).
53    pub workspace_root: PathBuf,
54    /// Model override from unit metadata, if any.
55    pub model: Option<String>,
56}
57
58/// Structured outcome from a worker run.
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct WorkerResult {
61    /// The unit ID.
62    pub unit_id: String,
63    /// Final status.
64    pub status: WorkerStatus,
65    /// Human-readable summary of what happened.
66    pub summary: Option<String>,
67    /// Error message if failed.
68    pub error: Option<String>,
69    /// Number of tool calls made.
70    pub tool_count: usize,
71    /// Number of agent turns.
72    pub turns: usize,
73    /// Total tokens used, if available.
74    pub tokens: Option<u64>,
75    /// Total cost, if available.
76    pub cost: Option<f64>,
77    /// Model used.
78    pub model: Option<String>,
79}
80
81/// Worker completion status.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "snake_case")]
84pub enum WorkerStatus {
85    /// Agent completed work and handed back a candidate result; verify has not
86    /// run yet. This is the pre-verification success stage on the path to
87    /// `Completed`, not a separate completion universe.
88    AwaitingVerify,
89    /// Agent completed work and verify passed.
90    Completed,
91    /// Agent hit a blocker it could not resolve.
92    Blocked,
93    /// Agent or verify failed.
94    Failed,
95    /// Run was cancelled.
96    Cancelled,
97}
98
99impl WorkerStatus {
100    pub fn as_str(self) -> &'static str {
101        match self {
102            Self::AwaitingVerify => "awaiting_verify",
103            Self::Completed => "completed",
104            Self::Blocked => "blocked",
105            Self::Failed => "failed",
106            Self::Cancelled => "cancelled",
107        }
108    }
109
110    pub fn lifecycle_label(self) -> &'static str {
111        match self {
112            Self::AwaitingVerify => "candidate complete ยท awaiting verify",
113            Self::Completed => "completed",
114            Self::Blocked => "blocked",
115            Self::Failed => "failed",
116            Self::Cancelled => "cancelled",
117        }
118    }
119}
120
121impl std::fmt::Display for WorkerStatus {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        f.write_str(self.lifecycle_label())
124    }
125}
126
127/// Minimal shared verification status for cross-boundary lineage.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
129#[serde(rename_all = "snake_case")]
130pub enum VerifierStatus {
131    Passed,
132    Failed,
133    Skipped,
134}
135
136/// Narrow first-pass artifact kinds that other runtimes can trust cold.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum ArtifactKind {
140    VerifyOutput,
141    DiffScopeSummary,
142    Patch,
143    ReviewRecord,
144    Log,
145    Other,
146}
147
148/// Reference to a durable artifact without embedding storage-heavy payloads.
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct ArtifactRef {
151    pub artifact_id: String,
152    pub kind: ArtifactKind,
153    /// Path, URI, or storage-specific lookup token.
154    pub locator: String,
155    pub run_id: Option<String>,
156    pub unit_id: Option<String>,
157    pub stage: Option<String>,
158}
159
160/// Minimal verifier result lineage shared across imp and mana.
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub struct VerifierResult {
163    pub verifier_name: String,
164    pub status: VerifierStatus,
165    pub command: Option<String>,
166    pub exit_code: Option<i32>,
167    pub summary: Option<String>,
168    pub artifact_refs: Vec<ArtifactRef>,
169    pub started_at: Option<String>,
170    pub finished_at: Option<String>,
171    pub run_id: Option<String>,
172    pub unit_id: Option<String>,
173}
174
175/// Reference-first evidence bundle shape; storage stays owned by mana.
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
177pub struct EvidenceBundleRef {
178    pub bundle_id: String,
179    pub unit_id: String,
180    pub run_id: Option<String>,
181    pub artifact_refs: Vec<ArtifactRef>,
182    pub summary: Option<String>,
183}
184
185/// Shared worker-facing contracts.
186pub mod worker {
187    pub use super::{WorkerAssignment, WorkerAttempt, WorkerResult, WorkerStatus};
188}
189
190/// Placeholder runner-facing contract modules.
191pub mod runner {}
192
193/// Shared evidence and artifact reference vocabulary.
194pub mod evidence {
195    pub use super::{ArtifactKind, ArtifactRef, EvidenceBundleRef, VerifierResult, VerifierStatus};
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn worker_status_serializes_as_snake_case() {
204        let json = serde_json::to_string(&WorkerStatus::AwaitingVerify).unwrap();
205        assert_eq!(json, "\"awaiting_verify\"");
206    }
207
208    #[test]
209    fn verifier_result_round_trips_with_artifact_refs() {
210        let verifier = VerifierResult {
211            verifier_name: "unit.verify".to_string(),
212            status: VerifierStatus::Failed,
213            command: Some("cargo test".to_string()),
214            exit_code: Some(1),
215            summary: Some("verify failed".to_string()),
216            artifact_refs: vec![ArtifactRef {
217                artifact_id: "artifact-1".to_string(),
218                kind: ArtifactKind::VerifyOutput,
219                locator: "mana://units/9/artifacts/verify-output".to_string(),
220                run_id: Some("run-1".to_string()),
221                unit_id: Some("9".to_string()),
222                stage: Some("verify".to_string()),
223            }],
224            started_at: None,
225            finished_at: None,
226            run_id: Some("run-1".to_string()),
227            unit_id: Some("9".to_string()),
228        };
229
230        let json = serde_json::to_string(&verifier).unwrap();
231        let round_trip: VerifierResult = serde_json::from_str(&json).unwrap();
232        assert_eq!(round_trip, verifier);
233    }
234}