Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crucible/src/loop_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ fn pass(v: Value) -> Attempt {

fn fail(cost_usd: f64, note: String) -> Attempt {
Attempt {
outcome: AttemptOutcome::Fail(note),
outcome: AttemptOutcome::fail(note),
cost_usd,
}
}
Expand Down
39 changes: 25 additions & 14 deletions crucible/src/manifest/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ pub enum WorkflowError {
EngineTaskInEpilogue { task: String },
#[error("report task {task:?} must run in the epilogue")]
ReportOutsideEpilogue { task: String },
#[error("epilogue task name {KEPT_INPUT:?} is reserved for the kept-candidate input")]
ReservedEpilogueName,
#[error("epilogue task name {name:?} is reserved for an input the engine writes")]
ReservedEpilogueName { name: String },
#[error(
"task {task:?} (stage {stage:?}) depends on {dependency:?} (stage {dependency_stage:?}); \
dependencies cannot cross stages"
Expand Down Expand Up @@ -453,8 +453,10 @@ impl WorkflowCfg {
task: task.name.0.clone(),
});
}
if task.name.0 == KEPT_INPUT {
return Err(WorkflowError::ReservedEpilogueName);
if task.name.0 == KEPT_INPUT || task.name.0 == crate::plan::exec::OUTCOME_INPUT {
return Err(WorkflowError::ReservedEpilogueName {
name: task.name.0.clone(),
});
}
}
for dependency in &task.depends_on {
Expand Down Expand Up @@ -1032,12 +1034,17 @@ mod tests {

#[test]
fn engine_tasks_cannot_be_epilogue() {
let mut workflow = full_autoresearch();
workflow.tasks[2].stage = Stage::Epilogue;
// A leaf: an engine task spliced into the epilogue with a dependency either way is a
// cross-stage edge, which the plan refuses before the stage rules are reached.
let workflow = parse(
"type = \"custom\"\nresult = \"check\"\n\
[[task]]\nname = \"check\"\nkind = \"evaluate\"\ncommand = \"true\"\n\
[[task]]\nname = \"grade\"\nkind = \"engine\"\nop = \"grade\"\nsource = \"check\"\nstage = \"epilogue\"\n",
);
assert_eq!(
workflow.validate().unwrap_err(),
WorkflowError::EngineTaskInEpilogue {
task: "score".to_owned()
task: "grade".to_owned()
}
);
}
Expand All @@ -1056,13 +1063,17 @@ mod tests {
}
);

let workflow = parse(
"[[task]]\nname = \"kept\"\nkind = \"command\"\ncommand = \"true\"\nstage = \"epilogue\"\n",
);
assert_eq!(
workflow.validate().unwrap_err(),
WorkflowError::ReservedEpilogueName
);
for reserved in [KEPT_INPUT, crate::plan::exec::OUTCOME_INPUT] {
let workflow = parse(&format!(
"[[task]]\nname = \"{reserved}\"\nkind = \"command\"\ncommand = \"true\"\nstage = \"epilogue\"\n"
));
assert_eq!(
workflow.validate().unwrap_err(),
WorkflowError::ReservedEpilogueName {
name: reserved.to_owned()
}
);
}
}

#[test]
Expand Down
7 changes: 2 additions & 5 deletions crucible/src/plan/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,12 +322,9 @@ fn render_mermaid_styled(
/// `finished`.
fn shutdown_outcome(out: &crate::plan::exec::PlanOutcome) -> &'static str {
use crate::plan::exec::PlanExit;
if out.valid {
return "finished";
}
match out.exit {
PlanExit::BudgetExceeded | PlanExit::TimeExceeded => "budget",
PlanExit::Completed | PlanExit::Truncated { .. } | PlanExit::ShortCircuit { .. } => "error",
PlanExit::Completed if !out.valid => "error",
ref exit => exit.shutdown_token(),
}
}

Expand Down
Loading
Loading