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