[修正] exit code: GATE拒否=1, 入力エラー=2 に統一 (#56, #57)

This commit is contained in:
林 駿甫 (Shunsuke Hayashi) 2026-04-10 06:48:55 +09:00
parent ca1c734bc8
commit 58c8bf71e1
2 changed files with 104 additions and 1 deletions

View file

@ -1298,6 +1298,10 @@ fn handle_gate_command(
emit_gate_error(format, "gate_rejected", &message);
1
}
Err(ProtocolError::DependencyBlocked(message)) => {
emit_gate_error(format, "gate_rejected", &message);
1
}
Err(ProtocolError::Input(message)) => {
emit_gate_error(format, "input_error", &message);
2

View file

@ -178,6 +178,28 @@ impl DeterministicExecutionProtocol {
node: &str,
files: &[String],
) -> ProtocolResult<AssignmentResult> {
let snapshot = self.snapshot_store.load().map_err(ProtocolError::from)?;
let blocked_by = snapshot
.get_task(task_id)
.ok_or_else(|| ProtocolError::input(format!("unknown task: {task_id}")))?
.dependencies
.iter()
.filter_map(|dependency| {
snapshot
.get_task(dependency)
.filter(|dep| {
!matches!(dep.current_state, TaskState::Done | TaskState::Merged)
})
.map(|_| dependency.clone())
})
.collect::<Vec<_>>();
if !blocked_by.is_empty() {
return Err(ProtocolError::dependency_blocked(format!(
"blocked by dependencies: {}",
blocked_by.join(", ")
)));
}
self.run(task_id, &[Gate::Gate2], agent, node)?;
let conflict = self
.lock_manager
@ -284,7 +306,9 @@ impl DeterministicExecutionProtocol {
node: &str,
) -> ProtocolResult<ExecutionTask> {
if merge_commit_sha.len() != 40 || !merge_commit_sha.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(ProtocolError::input("merge sha must be a 40-char hex string"));
return Err(ProtocolError::gate_rejected(
"merge sha must be a 40-char hex string",
));
}
self.update_task(task_id, actor, node, TaskEventType::MergeVerified, |task| {
@ -487,6 +511,8 @@ pub struct DispatchableReport {
pub enum ProtocolError {
#[error("gate rejected: {0}")]
GateRejected(String),
#[error("dependency blocked: {0}")]
DependencyBlocked(String),
#[error("input error: {0}")]
Input(String),
#[error("{0}")]
@ -498,6 +524,10 @@ impl ProtocolError {
Self::GateRejected(msg.into())
}
pub fn dependency_blocked(msg: impl Into<String>) -> Self {
Self::DependencyBlocked(msg.into())
}
pub fn input(msg: impl Into<String>) -> Self {
Self::Input(msg.into())
}
@ -735,4 +765,73 @@ mod tests {
Some("0123456789abcdef0123456789abcdef01234567")
);
}
#[test]
fn assign_returns_dependency_blocked_when_hard_dependency_is_unresolved() {
let (_tmp, protocol) = fixture();
protocol
.register(
RegisterTaskRequest {
task_id: "phase-a".into(),
title: "Phase A".into(),
dependencies: vec![],
soft_dependencies: vec![],
priority: 0,
completion_mode: CompletionMode::GithubPr,
},
"codex",
"macbook",
)
.unwrap();
protocol
.register(
RegisterTaskRequest {
task_id: "phase-b".into(),
title: "Phase B".into(),
dependencies: vec!["phase-a".into()],
soft_dependencies: vec![],
priority: 0,
completion_mode: CompletionMode::GithubPr,
},
"codex",
"macbook",
)
.unwrap();
let err = protocol
.assign(
"phase-b",
"codex",
"macbook",
&[String::from("crates/miyabi-core/src/protocol.rs")],
)
.unwrap_err();
assert!(matches!(err, ProtocolError::DependencyBlocked(_)));
}
#[test]
fn record_merge_treats_invalid_sha_as_gate_rejection() {
let (_tmp, protocol) = fixture();
protocol
.register(
RegisterTaskRequest {
task_id: "phase-a".into(),
title: "Phase A".into(),
dependencies: vec![],
soft_dependencies: vec![],
priority: 0,
completion_mode: CompletionMode::GithubPr,
},
"codex",
"macbook",
)
.unwrap();
let err = protocol
.record_merge("phase-a", "not-a-valid-sha", "codex", "macbook")
.unwrap_err();
assert!(matches!(err, ProtocolError::GateRejected(_)));
}
}