feat(932): add review_hold CRDT register + migrate callers off yaml_legacy
review_hold is now a typed bool register on PipelineItemCrdt alongside blocked / mergemaster_attempted. Exposed via the typed setter `crdt_state::set_review_hold(story_id, value)` and the `WorkItem::review_hold()` accessor. Replaces the legacy `review_hold: true` YAML front-matter field. Migrated callers: - http/mcp/qa_tools.rs::tool_approve_qa — clear via set_review_hold(false) - agents/lifecycle.rs::reject_story_from_qa — clear via set_review_hold(false) - agents/pool/pipeline/advance/helpers.rs::write_review_hold_to_store — set via set_review_hold(true), no more content rewrite - agents/pool/auto_assign/reconcile.rs (two callsites) — set via set_review_hold(true) instead of FS YAML write - agents/pool/auto_assign/story_checks.rs::has_review_hold — reads the typed register instead of conflating with Stage::Frozen (real bug fix: the legacy implementation returned `stage.is_frozen()`, which made the auto-assigner treat *every* held-for-review item as frozen even when it wasn't actually parked at the freeze stage). Dead yaml_legacy helpers removed: - write_review_hold(path), write_review_hold_in_content(content) - clear_front_matter_field(path) — last caller was the qa_tools wrap The yaml_residue marker doc now only mentions 933; the 932 line is gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -215,19 +215,8 @@ impl AgentPool {
|
||||
message: format!("Failed to advance to QA: {e}"),
|
||||
});
|
||||
} else {
|
||||
// Story 929 / sub-story 932: the review_hold signal still
|
||||
// lives in YAML on disk because no CRDT register exists.
|
||||
// Wrapped in `yaml_residue` so the gap is grep-findable.
|
||||
let story_path = project_root
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Err(e) = crate::db::yaml_legacy::yaml_residue(
|
||||
crate::db::yaml_legacy::write_review_hold(&story_path),
|
||||
) {
|
||||
eprintln!(
|
||||
"[startup:reconcile] Failed to set review_hold on '{story_id}': {e}"
|
||||
);
|
||||
}
|
||||
// Story 932: review_hold is a typed CRDT register.
|
||||
crate::crdt_state::set_review_hold(story_id, true);
|
||||
eprintln!(
|
||||
"[startup:reconcile] Moved '{story_id}' → 3_qa/ (qa: human — holding for review)."
|
||||
);
|
||||
@@ -289,18 +278,8 @@ impl AgentPool {
|
||||
};
|
||||
|
||||
if needs_human_review {
|
||||
// Story 929 / sub-story 932: see note above; review_hold
|
||||
// is YAML-only until the CRDT register exists.
|
||||
let story_path = project_root
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Err(e) = crate::db::yaml_legacy::yaml_residue(
|
||||
crate::db::yaml_legacy::write_review_hold(&story_path),
|
||||
) {
|
||||
eprintln!(
|
||||
"[startup:reconcile] Failed to set review_hold on '{story_id}': {e}"
|
||||
);
|
||||
}
|
||||
// Story 932: review_hold is a typed CRDT register.
|
||||
crate::crdt_state::set_review_hold(story_id, true);
|
||||
eprintln!(
|
||||
"[startup:reconcile] '{story_id}' passed QA — holding for human review."
|
||||
);
|
||||
|
||||
@@ -21,17 +21,15 @@ pub(super) fn read_story_front_matter_agent(
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Return `true` if the story is in the `Frozen` pipeline stage.
|
||||
/// Return `true` if the story has its `review_hold` CRDT register set.
|
||||
///
|
||||
/// In the typed CRDT model, `Frozen` is the authoritative representation of
|
||||
/// stories that are held for human review (replacing the legacy
|
||||
/// `review_hold: true` YAML front-matter field). The typed stage register is
|
||||
/// the only source consulted — stale YAML is ignored.
|
||||
/// Sub-story 932: `review_hold` is now a dedicated CRDT register on
|
||||
/// `PipelineItemCrdt`, distinct from `Stage::Frozen`. The auto-assigner uses
|
||||
/// this to keep human-QA items / spikes parked after gates pass until a
|
||||
/// reviewer explicitly clears the hold (e.g. via `tool_approve_qa`).
|
||||
pub(super) fn has_review_hold(_project_root: &Path, _stage_dir: &str, story_id: &str) -> bool {
|
||||
crate::pipeline_state::read_typed(story_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|item| item.stage.is_frozen())
|
||||
crate::crdt_state::read_item(story_id)
|
||||
.map(|w| w.review_hold())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -138,29 +136,42 @@ mod tests {
|
||||
// ── has_review_hold ───────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn has_review_hold_returns_true_when_frozen() {
|
||||
fn has_review_hold_returns_true_when_flag_set() {
|
||||
crate::crdt_state::init_for_test();
|
||||
crate::db::ensure_content_store();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
crate::db::write_item_with_content(
|
||||
"890_spike_frozen",
|
||||
"7_frozen",
|
||||
"---\nname: Frozen Spike\n---\n# Spike\n",
|
||||
crate::db::ItemMeta::named("Frozen Spike"),
|
||||
crate::crdt_state::write_item(
|
||||
"890_spike_held",
|
||||
"3_qa",
|
||||
Some("Held Spike"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(has_review_hold(tmp.path(), "3_qa", "890_spike_frozen"));
|
||||
crate::crdt_state::set_review_hold("890_spike_held", true);
|
||||
assert!(has_review_hold(tmp.path(), "3_qa", "890_spike_held"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_review_hold_returns_false_for_qa_stage() {
|
||||
fn has_review_hold_returns_false_when_flag_unset() {
|
||||
crate::crdt_state::init_for_test();
|
||||
crate::db::ensure_content_store();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
crate::db::write_item_with_content(
|
||||
crate::crdt_state::write_item(
|
||||
"890_spike_active_qa",
|
||||
"3_qa",
|
||||
"---\nname: Active QA Spike\n---\n# Spike\n",
|
||||
crate::db::ItemMeta::named("Active QA Spike"),
|
||||
Some("Active QA Spike"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(!has_review_hold(tmp.path(), "3_qa", "890_spike_active_qa"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user