huskies: merge 866
This commit is contained in:
@@ -72,6 +72,9 @@ pub fn project_stage(view: &PipelineItemView) -> Result<Stage, ProjectionError>
|
||||
match view.stage.as_str() {
|
||||
"0_upcoming" => Ok(Stage::Upcoming),
|
||||
"1_backlog" => Ok(Stage::Backlog),
|
||||
"2_blocked" => Ok(Stage::Blocked {
|
||||
reason: String::new(),
|
||||
}),
|
||||
"2_current" => Ok(Stage::Coding),
|
||||
"3_qa" => Ok(Stage::Qa),
|
||||
"4_merge" => {
|
||||
@@ -147,10 +150,11 @@ impl PipelineItem {
|
||||
let dir = stage_dir_name(&self.stage);
|
||||
let blocked = matches!(
|
||||
self.stage,
|
||||
Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
}
|
||||
Stage::Blocked { .. }
|
||||
| Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
}
|
||||
);
|
||||
// Frozen stories map to "7_frozen"; they are not "blocked" in the CRDT sense.
|
||||
(dir, blocked)
|
||||
@@ -302,6 +306,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_blocked_item() {
|
||||
let view = PipelineItemView {
|
||||
story_id: "42_story_test".to_string(),
|
||||
stage: "2_blocked".to_string(),
|
||||
name: Some("Test".to_string()),
|
||||
agent: None,
|
||||
retry_count: None,
|
||||
blocked: None,
|
||||
depends_on: None,
|
||||
claimed_by: None,
|
||||
claimed_at: None,
|
||||
merged_at: None,
|
||||
qa_mode: None,
|
||||
mergemaster_attempted: None,
|
||||
};
|
||||
let item = PipelineItem::try_from(&view).unwrap();
|
||||
assert!(matches!(item.stage, Stage::Blocked { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_archived_blocked_item() {
|
||||
let view = PipelineItemView {
|
||||
@@ -385,6 +409,13 @@ mod tests {
|
||||
(Stage::Upcoming, "0_upcoming", false),
|
||||
(Stage::Backlog, "1_backlog", false),
|
||||
(Stage::Coding, "2_current", false),
|
||||
(
|
||||
Stage::Blocked {
|
||||
reason: "stuck".into(),
|
||||
},
|
||||
"2_blocked",
|
||||
true,
|
||||
),
|
||||
(Stage::Qa, "3_qa", false),
|
||||
(
|
||||
Stage::Merge {
|
||||
|
||||
@@ -172,13 +172,7 @@ fn block_from_any_active_stage() {
|
||||
reason: "stuck".into(),
|
||||
},
|
||||
);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Ok(Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
})
|
||||
));
|
||||
assert!(matches!(result, Ok(Stage::Blocked { .. })));
|
||||
}
|
||||
|
||||
let m = Stage::Merge {
|
||||
@@ -191,17 +185,20 @@ fn block_from_any_active_stage() {
|
||||
reason: "stuck".into(),
|
||||
},
|
||||
);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Ok(Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
})
|
||||
));
|
||||
assert!(matches!(result, Ok(Stage::Blocked { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblock_returns_to_backlog() {
|
||||
fn unblock_returns_to_coding() {
|
||||
let s = Stage::Blocked {
|
||||
reason: "test".into(),
|
||||
};
|
||||
let result = transition(s, PipelineEvent::Unblock).unwrap();
|
||||
assert!(matches!(result, Stage::Coding));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_unblock_archived_blocked_returns_to_backlog() {
|
||||
let s = Stage::Archived {
|
||||
archived_at: chrono::Utc::now(),
|
||||
reason: ArchiveReason::Blocked {
|
||||
|
||||
@@ -160,14 +160,11 @@ pub fn transition(state: Stage, event: PipelineEvent) -> Result<Stage, Transitio
|
||||
reason: ArchiveReason::Completed,
|
||||
}),
|
||||
|
||||
// ── Stuck states (any active → Archived) ───────────────────────
|
||||
// ── Block: any active → Blocked ──────────────────────────────
|
||||
(Backlog, Block { reason })
|
||||
| (Coding, Block { reason })
|
||||
| (Qa, Block { reason })
|
||||
| (Merge { .. }, Block { reason }) => Ok(Archived {
|
||||
archived_at: now,
|
||||
reason: ArchiveReason::Blocked { reason },
|
||||
}),
|
||||
| (Merge { .. }, Block { reason }) => Ok(Blocked { reason }),
|
||||
|
||||
(Backlog, ReviewHold { reason })
|
||||
| (Coding, ReviewHold { reason })
|
||||
@@ -221,7 +218,10 @@ pub fn transition(state: Stage, event: PipelineEvent) -> Result<Stage, Transitio
|
||||
merge_commit: GitSha("closed".to_string()),
|
||||
}),
|
||||
|
||||
// ── Unblock: from Archived(Blocked) or Archived(MergeFailed) → Backlog
|
||||
// ── Unblock: Blocked → Coding ─────────────────────────────────
|
||||
(Blocked { .. }, Unblock) => Ok(Coding),
|
||||
|
||||
// ── Legacy unblock: Archived(Blocked|MergeFailed) → Backlog ──
|
||||
(
|
||||
Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
|
||||
@@ -47,7 +47,7 @@ impl fmt::Display for AgentName {
|
||||
/// after CRDT convergence. Notice what is NOT a field:
|
||||
/// - `agent` — local execution state, not pipeline state
|
||||
/// - `retry_count` — also local
|
||||
/// - `blocked` — folded into `Archived { reason: Blocked { .. } }`
|
||||
/// - `blocked` — now a first-class `Blocked { reason }` stage
|
||||
///
|
||||
/// ## Canonical state machine (story 857)
|
||||
///
|
||||
@@ -61,7 +61,7 @@ impl fmt::Display for AgentName {
|
||||
/// | qa_pending | `Qa` |
|
||||
/// | merge_pending | `Merge { .. }` |
|
||||
/// | done | `Done { .. }` |
|
||||
/// | blocked | `Archived { Blocked { .. } }` |
|
||||
/// | blocked | `Blocked { .. }` |
|
||||
/// | merge_failure | `Archived { MergeFailed { .. } }` |
|
||||
/// | archived | `Archived { Completed }` |
|
||||
/// | superseded | `Archived { Superseded { .. } }` |
|
||||
@@ -95,6 +95,11 @@ pub enum Stage {
|
||||
merge_commit: GitSha,
|
||||
},
|
||||
|
||||
/// Story is blocked, awaiting human resolution or retry-limit review.
|
||||
/// Unlike `Archived`, a blocked story is expected to return to active work
|
||||
/// (via `Unblock → Coding`).
|
||||
Blocked { reason: String },
|
||||
|
||||
/// Out of the active flow. The reason explains why.
|
||||
Archived {
|
||||
archived_at: DateTime<Utc>,
|
||||
@@ -149,14 +154,16 @@ impl Stage {
|
||||
stage_dir_name(self)
|
||||
}
|
||||
|
||||
/// Returns true if this is the Archived(Blocked) variant.
|
||||
/// Returns true if this is the `Blocked` variant (or the legacy
|
||||
/// `Archived(Blocked)` for backward-compatible reads).
|
||||
pub fn is_blocked(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
}
|
||||
Stage::Blocked { .. }
|
||||
| Stage::Archived {
|
||||
reason: ArchiveReason::Blocked { .. },
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -172,6 +179,9 @@ impl Stage {
|
||||
match s {
|
||||
"0_upcoming" => Some(Stage::Upcoming),
|
||||
"1_backlog" => Some(Stage::Backlog),
|
||||
"2_blocked" => Some(Stage::Blocked {
|
||||
reason: String::new(),
|
||||
}),
|
||||
"2_current" => Some(Stage::Coding),
|
||||
"3_qa" => Some(Stage::Qa),
|
||||
"4_merge" => Some(Stage::Merge {
|
||||
@@ -269,6 +279,7 @@ pub fn stage_label(s: &Stage) -> &'static str {
|
||||
Stage::Qa => "Qa",
|
||||
Stage::Merge { .. } => "Merge",
|
||||
Stage::Done { .. } => "Done",
|
||||
Stage::Blocked { .. } => "Blocked",
|
||||
Stage::Archived { .. } => "Archived",
|
||||
Stage::Frozen { .. } => "Frozen",
|
||||
}
|
||||
@@ -280,6 +291,7 @@ pub fn stage_dir_name(s: &Stage) -> &'static str {
|
||||
Stage::Upcoming => "0_upcoming",
|
||||
Stage::Backlog => "1_backlog",
|
||||
Stage::Coding => "2_current",
|
||||
Stage::Blocked { .. } => "2_blocked",
|
||||
Stage::Qa => "3_qa",
|
||||
Stage::Merge { .. } => "4_merge",
|
||||
Stage::Done { .. } => "5_done",
|
||||
|
||||
Reference in New Issue
Block a user