1use super::{
4 AgentMission, GuardCapability, HumanDecisionPoint, MissionRun, WorkspaceLease,
5 WorkspaceLeaseConflict, WorkspaceLeaseMode, detect_workspace_lease_conflicts,
6};
7use sim_kernel::{Expr, Result, Symbol};
8use sim_lib_stream_core::{DevCassette, DevEvent, LatencyClass};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MissionControlFixture {
13 pub mission: AgentMission,
15 pub cassette: DevCassette,
17 pub conflicts: Vec<WorkspaceLeaseConflict>,
19 pub snapshot: Expr,
21}
22
23pub fn fake_mission_control_fixture() -> Result<MissionControlFixture> {
25 let mission = mission();
26 let conflicting = AgentMission::new(
27 Symbol::qualified("agent/mission", "docs-refresh"),
28 "sim-agent-net",
29 )
30 .with_goal("Refresh generated docs")
31 .with_lease(WorkspaceLease::crate_name("sim-agent-net", "sim-lib-agent"));
32 let conflicts = detect_workspace_lease_conflicts(&[mission.clone(), conflicting]);
33 let cassette = evidence_cassette(mission.evidence_stream().clone())?;
34 let snapshot = mission_snapshot(&mission, &conflicts);
35 Ok(MissionControlFixture {
36 mission,
37 cassette,
38 conflicts,
39 snapshot,
40 })
41}
42
43fn mission() -> AgentMission {
44 AgentMission::new(
45 Symbol::qualified("agent/mission", "mission-control-fixture"),
46 "sim-agent-net",
47 )
48 .with_goal("Render Mission Control")
49 .with_scope_summary("agent mission state and deterministic evidence")
50 .with_capability(GuardCapability::EditRepo("sim-agent-net".to_owned()))
51 .with_capability(GuardCapability::RunValidation("sim-agent-net".to_owned()))
52 .with_capability(GuardCapability::RegenDocs("sim-agent-net".to_owned()))
53 .with_lease(WorkspaceLease::crate_name("sim-agent-net", "sim-lib-agent"))
54 .with_lease(
55 WorkspaceLease::file(
56 "sim-agent-net",
57 "crates/sim-lib-agent/src/atelier/mission_control.rs",
58 )
59 .for_read(),
60 )
61 .with_lease(WorkspaceLease::ide_object(Symbol::qualified(
62 "ide/object",
63 "agent-mission-control",
64 )))
65 .with_validation(MissionRun::new(
66 "agent-view-tests",
67 "cargo test -p sim-lib-view-agent mission_control",
68 ))
69 .with_docs_run(MissionRun::new(
70 "simdoc",
71 "cargo run -p xtask -- simdoc --check",
72 ))
73 .with_decision_point(HumanDecisionPoint::new(
74 "approve",
75 "Approve the Mission Control change",
76 ))
77 .with_recipe_pattern(Symbol::new("a30-009-agentic-workflow"))
78}
79
80fn evidence_cassette(stream_id: Symbol) -> Result<DevCassette> {
81 let node = Symbol::qualified("atelier/agent", "mission-control");
82 let events = vec![
83 DevEvent::new(
84 "retrieval",
85 node.clone(),
86 LatencyClass::OfflineRender,
87 payload("Cartographer ranked Mission Control context"),
88 )?,
89 DevEvent::new(
90 "guard",
91 node.clone(),
92 LatencyClass::Interactive,
93 payload("Guard accepted sim-agent-net lease"),
94 )?,
95 DevEvent::validate(
96 node.clone(),
97 payload("cargo test -p sim-lib-view-agent mission_control"),
98 )?,
99 DevEvent::new(
100 "human-gate",
101 node.clone(),
102 LatencyClass::Interactive,
103 payload("approval pending"),
104 )?,
105 DevEvent::new(
106 "reflect",
107 node,
108 LatencyClass::OfflineRender,
109 payload("F6 attribution cites cassette evidence"),
110 )?,
111 ];
112 DevCassette::from_events(stream_id, events)
113}
114
115fn mission_snapshot(mission: &AgentMission, conflicts: &[WorkspaceLeaseConflict]) -> Expr {
116 Expr::Map(vec![
117 key("id", Expr::Symbol(mission.id().clone())),
118 key("goal", Expr::String(mission.goal().to_owned())),
119 key(
120 "recipe-pattern",
121 Expr::Symbol(mission.recipe_pattern().clone()),
122 ),
123 key(
124 "roles",
125 Expr::List(
126 mission
127 .roles()
128 .iter()
129 .map(|role| Expr::Symbol(role.as_symbol()))
130 .collect(),
131 ),
132 ),
133 key(
134 "validations",
135 Expr::List(
136 mission
137 .validations()
138 .iter()
139 .map(|run| Expr::String(run.label.clone()))
140 .collect(),
141 ),
142 ),
143 key(
144 "human-gates",
145 Expr::List(
146 mission
147 .decision_points()
148 .iter()
149 .map(|point| Expr::String(point.id.clone()))
150 .collect(),
151 ),
152 ),
153 key(
154 "conflicts",
155 Expr::List(conflicts.iter().map(conflict_expr).collect()),
156 ),
157 ])
158}
159
160fn conflict_expr(conflict: &WorkspaceLeaseConflict) -> Expr {
161 Expr::Map(vec![
162 key("left-mission", Expr::Symbol(conflict.left_mission.clone())),
163 key(
164 "right-mission",
165 Expr::Symbol(conflict.right_mission.clone()),
166 ),
167 key("left-target", Expr::String(conflict.left.target.clone())),
168 key("right-target", Expr::String(conflict.right.target.clone())),
169 key(
170 "mode",
171 Expr::Symbol(Symbol::new(match conflict.left.mode {
172 WorkspaceLeaseMode::SharedRead => "shared-read",
173 WorkspaceLeaseMode::ExclusiveWrite => "exclusive-write",
174 })),
175 ),
176 ])
177}
178
179fn payload(summary: &str) -> Expr {
180 Expr::Map(vec![key("summary", Expr::String(summary.to_owned()))])
181}
182
183fn key(name: &str, value: Expr) -> (Expr, Expr) {
184 (Expr::Symbol(Symbol::new(name)), value)
185}